eijensonの日記

主に作業中にはまったことの作業ログを書いていきます。

クライアント側の文字数制限を書ける時に悩むこと

Android/iOSのネイティブアプリやフロントエンドでテキストフィールドを設置する際、文字数にバリデーションをかける事がある。

その扱いについて少し悩んでいるので考えを列挙する

色んな所で言われていることをまとめてるだけ。

何に悩んでいるのか

・ネイティブ側でこのバリデーションをかける必要があるのか?

・テキストフィールドにその文字数以上入力できないようにするのか、送信時にチェックをしてエラーにするのがいいのか

ネイティブ側でこのバリデーションをかける必要があるのか?

この文字数制限は、無制限に送れるようにしてしまうとサーバやDBの負荷になるため書けることがほとんどだと思う。

そのため、必ずサーバ側で受け取った時にも文字数にバリデーションはかける。(クライアント側しかないならそれは不具合と言っていい)

そうなると、ネイティブ側はなにもしなくてもエラーハンドリングさえしていればいいことになる。

これに関しては、わざわざ通信しないとエラー結果が出ないことが気になるなら文字数チェックをかけたほうがいい。

ユーザの利便性を考えていくと、通信しなくてもエラーがわかるならわかったほうがいい。

文字数の数をころころ変えて仕様の調整をしたいようならサーバ側のみじゃないとだめだけど。

テキストフィールドにその文字数以上入力できないようにするのか、送信時にチェックをしてエラーにするのがいいのか

文字数制限をネイティブ側で掛ける場合、maxlength等でテキストフィールドにその文字数以上入力させない方法がある。

一方で、送信ボタン押した時にjavascriptやネイティブコード上で文字数をチェックして、超えるならエラーにするという方法がある。

これに関しては悩みが多くて、

maxlengthを使う方法は、ユーザがコピペでテキストを入力した場合、ペーストする文字列の数が多い場合勝手に切られちゃうため(100文字の制限がかかってるテキストフィールドに101文字をペーストすると最後の文字は切られて無くなるため)不具合というか、ユーザの意図に反する動きになる。

maxlengthをパスワードにかけた結果、意図したパスワードとは違うパスワードが設定されてトラブルになったケースをブログで見たことがあるのであまり使わないほうが良さそう。

送信ボタン押した時にチェックするのはわりと普通のやり方でいい。仕様としてそこの文字数制限が変わらないなら問題ない。

ちょっとエラーにするためのラベル欄の出し分けとかがめんどくさいくらい。

他には10/100的な感じで入力文字数/文字数制限を可視化して超えるなら赤くするのもあって、リッチでいい。

まとめとしては

・サーバに文字数制限はかならず書け、クライアントだけにしない

・クライアントでは利便性を良くするのを目的にする

になる

Firefoxのヘッドレスブラウザ+Seleniumでログイン後ページのスクリーンショットを撮る

目的

サービスの計測値を日時で確認必要があり、それを自動化したかった

APIが提供されていないので直接見るしかなく、

ログインが必要なためnokogiriによるスクレイピングでは難しかった

そのためSeleniumを使ってログインし、スクリーンショットを定期的に撮るようにした

コード

screen_shot.rb

    require 'selenium-webdriver'
    require 'time'

    url = "...ログインが必要なサービスのログインページ" #適宜変更
    options = Selenium::WebDriver::Firefox::Options.new
    options.add_argument('-headless')

    driver = Selenium::WebDriver.for :firefox, options: options
    driver.navigate.to url

    sleep 3 #seleniumはブラウザ表示が間に合わなくてエラーになるため3秒待つ
    email = driver.find_element(id: 'email')#DevToolでID入力欄を指定できるID/Classを探す
    email.send_keys "ID"#IDを入力
    password = driver.find_element(id: 'password')#DevToolでパスワードを指定できるID/Classを探す
    password.send_keys "パスワード"#パスワードを入力
    submit = driver.find_element(class: "sign-in")#DevToolでログインボタンを指定できるID/Classを探す
    submit.submit
    sleep 7 #遷移時の処理が遅い場合があるため待つ
    date = Time.now.strftime("%Y%m%d_%H%M%S")
    driver.save_screenshot('log_' + date + '.png')

    driver.quit

Gemfile

source 'https://rubygems.org'
gem 'selenium-webdriver'

コンソールコマンド

bundle install
bundle exec ruby screen_shot.rb

実行したディレクトリ内にlog_{現在時刻}.png というファイルが表示されます。 例えばGmailみたいに、「未ログイン時に遷移した時に、ログインページに飛び、ログイン後にもともと遷移したかったページが表示される」サービスの場合は表示したいページを直接指定すればそのスクリーンショットが取れる

shellでフォルダ内の全てのファイルに対してfor処理をする

目的

screenshot
├── スクリーンショット10月
│   └── 01.png
│   ├── 02.png
│   ├── 03.png
│   ├── 04.png
│   ├── 05.png
│   ├── 06.png
│   ├── 07.png
│   └── 08.png
└── スクリーンショット11月
    └── 01.png
    ├── 02.png
    ├── 03.png
    ├── 04.png
    ├── 05.png
    ├── 06.png
    ├── 07.png
    └── 08.png

こんな感じのディレクトリ構成になっていて、各フォルダ内の全ての画像をjpgに変えたかった

作成したshell

#!/bin/bash
#set -eux

while read -d $'\0' d; do
    dir=`basename "${d}"`
    echo dir="${dir}" 
    while read -d $'\0' file; do
        echo file="${file}" 
        convert "${file}" -quality 100 "${file}"
    done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0)
done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)

説明

while read -d $'\0' d; do
  #今いる場所のフォルダ分loopを回す
 # ${d}でフォルダのpathが取れる
done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)

while read -d $'\0' d; do は正直良くわかっていないが、いろいろ試した結果ディレクトリ内のすべてのファイルをループする - Qiitaの記事で書かれているやり方が一番良く動いたので使っている

done < <(find . -type d -mindepth 1 -maxdepth 1 -print0)

find . で今いる場所を検索する

-type d でディレクトリのみに検索範囲を指定

-mindepth 1 -maxdepth 1 で今いるディレクトリのみを検索範囲にする(深く探さない)

-print0 で空白文字を含むディレクトリも検索できるようにする参考URL→findとxargsコマンドで-print0オプションを使う理由(改) - Qiita

中身のloop

 dir=`basename "${d}"`
    echo dir="${dir}" 
    while read -d $'\0' file; do
        echo file="${file}" 
        convert "${file}" -quality 100 "${file}"
    done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0)

dir=`basename "${d}"` ディレクトリパスからディレクトリ名を取得している

done < <(find "$dir" -mindepth 1 -maxdepth 1 -print0) で-type d をなくしてloopを回しているため、ファイルも検索対象になっている

ファイルは画像のみなので、convert "${file}" -quality 100 "${file}"ImageMagickのconvertコマンドを叩いて変換ができる

Android+Jacocoでテストカバレッジを出力する(Android Studio3.2対応)

しばらく放置していたJacocoでのカバレッジ取得を更新しようとしたらハマった。

参考にしたサイト: http://phicdy.hatenablog.com/entry/jacoco-code-coverage

上の記事が一年前に書かれているが、Gradleの更新によって動かなくなったらしい

タスク自体は成功するが、出力結果が0件となっていた

    classDirectories = files(
                fileTree(
-                        dir: "${buildDir}/intermediates/classes/uiTest/debug",
+                        dir: "${buildDir}/intermediates/javac/uiTestdebug",
                        exclude: coverageExcludeFiles))
    }

jacocoのカバレッジ計測に使っている、classファイル群が生成されるフォルダを指定してあげたら動いた

RxJava2へ移行時のUnitTest

RxJava1 から2へ移行すると、TestSubscriberがうまく使えなくなった。

代わりにObservar.test()というメソッドが追加された。

RxJava1

public void test(){
//...

        TestSubscriber<UserResult> testSubscriber = TestSubscriber.create();
        repository.getUser().subscribe(testSubscriber);
        testSubscriber.awaitTerminalEvent();
        testSubscriber.assertCompleted();
        UserResult result = testSubscriber.getOnNextEvents().get(0);

        assertEquals(result.getName(), "田中");
        assertEquals(result.getAge(), 13);
//...
}

RxJava2

public void test(){
//...

        UserResult result = repository
                .getUser()
                .test()
                .assertComplete()
                .assertValueCount(1)
                .values().get(0);

        assertEquals(result.getName(), "田中");
        assertEquals(result.getAge(), 13);
//...
}

メソッドチェーンで記述できることで、型指定の煩わしさが少なくなる。

JobSchedulerで◯時間後からXX時間ごとに定期実行する方法

概要

Android Oreo対応の時に、定期実行処理をJobSchedulerに設定したら1回目が即時実行されて困ったので対応した。

コード

アプリ起動のActivity

class MainActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        // 略
        startJob()
    }

    override fun startJob() {
        val componentName = ComponentName(this, FirstRunJobService::class.java)

        val periodic = 15 * 60 * 1000L // 15分
        val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
        val jobInfo = JobInfo.Builder(1, componentName)
                .setMinimumLatency(periodic) // サービス起動遅延時間を設定する
                .build()

        scheduler.schedule(jobInfo)
    }
}

Activityから起動するService

class FirstRunJobService : JobService() {
    override fun onStartJob(p0: JobParameters?): Boolean {
        startJob()
        return false
    }

    override fun onStopJob(p0: JobParameters?): Boolean {
        return false
    }

    private fun startJob() {
        val componentName = ComponentName(this, MyJobService::class.java)

        // デバッグ時は15分,リリース版は6時間ごと
        val periodic = if (BuildConfig.DEBUG) 15 * 60 * 1000L else 6 * 60 * 60 * 1000L
        val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
        val jobInfo = JobInfo.Builder(2, componentName)
                .setPeriodic(periodic) // サービス実行間隔を設定する
                .build()

        scheduler.schedule(jobInfo)
    }
}

定期実行するService

class MyJobService : JobService() {

    override fun onStartJob(p0: JobParameters?): Boolean {
        // 定期実行したい処理
        return true
    }

    override fun onStopJob(p0: JobParameters?): Boolean {
        return true
    }
}

説明

ActivityでFirstRunJobServiceを15分後に起動するように処理を書く。 setMinimumLatencyメソッドで遅延時間を設定する(コードでは15分後に実行する)

FirstRunJobServiceでは、MyJobServiceを起動するための設定だけを行なっている setPeriodicメソッドで実行間隔を設定する(コードでは15分か6時間後に実行する)

setMinimumLatencyとsetPeriodicは同時には設定できない(例外が吐かれる)

参考URL

公式のリファレンス

https://developer.android.com/reference/android/app/job/JobInfo.Builder

Windowsにて、SourceTreeでcloneしようとしてエラーになった時の対処法

Macの方でも使っているSourceTreeでCloneしようとしたら結構てこずったのでメモする

環境

  • Windows10
  • SourceTree:2.5.5.0
  • GitHubにレポジトリがある
  • sshでcloneしたい

エラー文

コマンド: git -c diff.mnemonicprefix=false -c core.quotepath=false ls-remote git@github.com:tyutyu08/connpass_searcher.git
出力: 
エラー: Server refused our key
FATAL ERROR: Disconnected: No supported authentication methods available (server sent: publickey)
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
  • 検索したら「SourceTreeから再生成すればできるよ」との記事がいくつか見つかったけど、できなかった
  • GitBashでCloneしてみると以下のエラーが発生
    • Permission denied (publickey).
  • 調べるとGitHubに方法が書いてあった
  • GitBash上で作りなおしたキーを使うとCloneできた。
  • 後はSouceTreeの設定で、「ツール→オプション→全般→SSHクライアントの設定」で
    • SSH キー:作り直したキー
    • SSH クライアント:OpenSSH に変更したらFetch等レポジトリ操作ができるようになった。