20190709のAndroidに関する記事は10件です。

RyzenでのAVDでつまづいた話

Xamarin.Formsで初めてモバイルアプリ開発をしようとした矢先、いきなり大量のエラーメッセージが...image.png

Android Studioはどうだ??と試してみてもエラーメッセージの嵐...
image.png
初めてのモバイル開発がこんなスタートだったら萎えますね。このエラーを治すために2日間ハマってしまいました...

環境

CPU:Ryzen1700
OS:Windows10 Pro-バージョン1903

問題

Xamarin.Forms,Android Studioでエミュレーターでのデバッグができない...

解決

この問題はRyzenシリーズを使っているWindows10バージョン1903に起こるバグらしいです。解決策は単純。前のバージョンに戻す、またはWindows Insider Previewに登録するかです。
僕の場合、バージョン1903にしてから10日以上たっていてバージョンダウンできなかったので泣く泣くInsider Previewに...
プレリリースのバージョンなので不安定だけど、とりあえず無事(?)にAVDができるようになりました。
スクリーンショット (4).png
早くWindows公式バージョンでもこのバグ直してほしいですね....Microsoftさん頼みます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ナビゲーションドローワーのUIテスト

EspressoでUIテストを実施していて、ふと、ナビゲーションドローワーがある場合はどうなるんだろうと思い、やってみました。

dependencyの追加

ナビゲーションドローワーのアクションはespresso-contribのライブラリに入っているので追加しました。
その時、バージョン違いでコンフリクトが起こったので、重複しているものは除外します。

build.gradle(app)
androidTestImplementation ('com.android.support.test.espresso:espresso-contrib:3.0.2') {
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'recyclerview-v7'
        exclude group: 'com.android.support', module: 'appcompat-v7'
        exclude group: 'com.android.support', module: 'design'
        exclude group: 'com.android.support', module: 'support-compat'
        exclude group: 'com.android.support', module: 'support-core-utils'
    }

これでボタンクリックと同じようにナビゲーションドローワーを開くアクションが使えるようになります。

テストの記載

NavigatinViewは下記になります。

activity_main.xml
<android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />
MainActivityTest
    @Test
    fun navigationMenu_updatetime_exist() {
        onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
        Thread.sleep(1500)
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_slideshow))
        Thread.sleep(1500)
        onView(ViewMatchers.withId(R.id.updateTimeLabel)).check(matches(not(doesNotExist())))
    }

ナビゲーションドローワーを開くアクションはDrawerActions.open()になります。
開いたら、NavigationViewActions.navigateTo(R.id.nav_slideshow)の部分で、開いたドローワーに存在するメニューをタップしています。

実際にテストしてみて、思ったより画面の操作は担保できるなあと感じました。

参照

[Espresso] NavigationViewをNavigationViewActionsで操作してみようの話

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EspressoでRecyclerViewのチェックを行うカスタムMatcherを作成

背景

RecyclerViewのUIテストを作ろうとすると、どうしても標準APIでは満たせないテスト項目が出てきます。
https://qiita.com/AAkira/items/26ee7b18b11635cfc621 を参考にさせていただき、独自のRecyclerViewMatcherを作成しました。
下記の項目ではそれぞれバラバラに記載していますが、全てのメソッドは1つのRecyclerViewMatcherの中に定義されています。

実装

【isExist】RecyclerViewの指定した位置に、指定したViewHolderが存在すること

RecyclerViewMatcher.kt
object RecyclerViewMatcher {
    inline fun <reified T : RecyclerView.ViewHolder> isExist(position: Int) = object : TypeSafeMatcher<View>() {
        override fun describeTo(description: Description) {
            description.appendText("RecyclerViewの指定した位置に、指定したViewHolderが存在すること")
        }

        override fun matchesSafely(view: View): Boolean {
            if (view !is RecyclerView) return false
            return view.findViewHolderForAdapterPosition(position) is T
        }
    }
}

【contains】RecyclerViewの指定した範囲内に、指定したViewHolderが存在すること

RecyclerViewMatcher.kt
object RecyclerViewMatcher {
    inline fun <reified T : RecyclerView.ViewHolder> contains(range: IntRange) = object : TypeSafeMatcher<View>() {
        override fun describeTo(description: Description) {
            description.appendText("RecyclerViewの指定した範囲内に、指定したViewHolderが存在すること")
        }

        override fun matchesSafely(view: View): Boolean {
            if (view !is RecyclerView) return false
            return range.any { position ->
                view.findViewHolderForAdapterPosition(position) is T
            }
        }
    }
}

【isVisible】RecyclerViewの指定した位置のViewHolderに、指定したViewが表示されていること

RecyclerViewMatcher.kt
object RecyclerViewMatcher {
    inline fun <reified T : RecyclerView.ViewHolder> isVisible(position: Int, @IdRes id: Int) = object : TypeSafeMatcher<View>() {
        override fun describeTo(description: Description) {
            description.appendText("RecyclerViewの指定した位置のViewHolderに、指定したViewが表示されていること")
        }

        override fun matchesSafely(view: View): Boolean {
            if (view !is RecyclerView) return false
            val viewHolder = view.findViewHolderForAdapterPosition(position) as? T ?: return false

            return viewHolder.itemView.findViewById<View>(id)?.visibility == View.VISIBLE
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Espressoを用いたUIテストで、指定のViewが表示されるまで待機する

背景

これまで、EspressoのUIテストにおけるネットワーク通信待機処理をThread.sleep()で行なっていました。
この方法では、通信が完了した後も指定した時間待つことになり、テスト実行に余計な時間が掛かってしまいます。
これを改善するために、今回はUiAutomatorを利用して指定したViewが表示されたタイミングで待機処理を終了する実装に置き換えました。

実装

まずは、UiAutomatorの依存関係を追加します。

build.gradle
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'

そして、Kotlinの拡張関数を用いてトップレベル関数として待機処理を実装します。
テスト設計にはPageObjectパターンを適用しているため、戻り値にはthisを指定してレシーバを返すようにしています。
※PageObjectパターンはDroidKaigi2019のこちらのセッションを参考にさせていただきました。(https://droidkaigi.jp/2019/timetable/70791/

PageObjectExt.kt
/**
 * [resId]に指定したViewが表示されるまで待つ
 * [timeout]はデフォルトで7秒
 */
fun <T : Any> T.waitShown(resources: Resources, @IdRes resId: Int, timeout: Long = 7_000): T {
    UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
            .wait(Until.findObject(By.res(
                    "[YOUR_APP_PACKAGE_NAME]",
                    resources.getResourceEntryName(resId))),
                    timeout)
    return this
}

テストメソッドで使うとこのようになります。
画面の挙動としては、SampleActivityで通信を行い、通信中はローディングが表示され、通信完了後にtarget_viewが表示されるイメージです。

SampleActivityTest.kt
@RunWith(AndroidJUnit4::class)
@LargeTest
class SampleActivityTest {
    @Rule
    @JvmField
    val activityRule: ActivityTestRule<SampleActivity> = ActivityTestRule(SampleActivity::class.java)

    @Test
    fun test() {
        val resources = activityRule.activity.resources

        SampleActivityPage
                .fetchData() // 時間の掛かる通信処理
                .waitShown(resources, R.id.target_view) // `target_view`が表示されるまで待機
                .assert() // 任意のアサーション
    }
}

解説

waitShownの処理を分解して見てみます。

PageObjectExt.kt
/**
 * [resId]に指定したViewが表示されるまで待つ
 * [timeout]はデフォルトで7秒
 */
fun <T : Any> T.waitShown(resources: Resources, @IdRes resId: Int, timeout: Long = 7_000): T {
    // デバイスの状態を提供するクラス。ホームボタンなどの制御も可能。
    val uiDevice: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

    // UI要素を指定するクラス。パッケージ名とリソースID名で指定する。
    val bySelector: BySelector = By.res("[YOUR_APP_PACKAGE_NAME]", resources.getResourceEntryName(resId))

    // waitメソッドに渡す条件。最初に一致するUI要素(BySelector)を条件として返す。
    val searchCondition: SearchCondition<UiObject2> = Until.findObject(bySelector)

    // 条件(SearchCondition)に一致するか、タイムアウトになるまで待機する。
    uiDevice.wait(searchCondition, timeout)

    return this
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Kotlin+DataBinding+ViewModelでとりあえず動くモノを作る

概要

AndroidのDataBindingやViewModelの実装について手続き的な面でつまづいたことがあったので自分なりにまとめました!
「とにかく動かすための手続きが知りたい」という方に向けてサンプルコードを具体的に記述します。

作るもの

ボタンのタップによって画像の表示⇔非表示がトグルするやつを作ります!

作りましょう

プロジェクトを作ろう

今回はこれでいきます!
ファイル名

登場人物

プロジェクトを作ったらこの人達をいじっていきます!

  • build.gradle(app)
  • MainViewModel.kt
  • MainActivity.kt
  • layout/activity_main.xml

build.gradle(app)

dataBinding{ enabled = true } をいい感じの場所に追加します!

MainViewModel.kt

android.arch.lifecycle.ViewModelを継承したMainViewModelを新規作成します!
こいつにデータ(今回だとフラグ)を保持させます。
toggle()が呼び出される度に_flag.valueがtrue⇔falseと切り替わるイメージです。

MainViewModel.kt
class MainViewModel : ViewModel() {
    private val _flag = MutableLiveData<Boolean>().apply {
        value = false
    }

    val flag: LiveData<Boolean>
        get() = _flag

    fun toggle() {
        _flag.postValue(!(flag.value ?: false))
    }
}

activity_main.xml

レイアウトファイルと、ついでにstringsファイルを弄ります!
バインディングされたオブジェクトのgetterは勝手に剥いてくれるので、三項演算子の部分はviewModel.flagと書いています。
viewModel.getFlag().getValue()でも動きます)

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
                name="viewModel"
                type="com.rodentia6.sampledatabinding.MainViewModel"/>
        <import type="android.view.View"/>
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

        <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="@{viewModel.flag ? View.VISIBLE : View.GONE}"
                app:srcCompat="@android:drawable/star_on"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintBottom_toTopOf="@id/button"
                app:layout_constraintLeft_toLeftOf="parent"
                android:contentDescription="@null"/>

        <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="80dp"
                android:text="@{viewModel.flag ? @string/kesu : @string/tukeru}"
                android:onClick="@{(v) -> viewModel.toggle()}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

    </android.support.constraint.ConstraintLayout>
</layout>
values/strings.xml
<resources>
    <string name="app_name">SampleDataBinding</string>
    <string name="tukeru">つける</string>
    <string name="kesu">けす</string>
</resources>

一旦リビルドをかける

DataBindingのクラスを自動生成してもらうために一旦リビルトします!(上メニューから Build > Rebuild Project)
何かが間違っていた場合はこの時点でエラーが発生します。

MainActivity.kt

Activityのクラスを編集します!
ViewModelオブジェクトとDataBindingオブジェクトを作成して色々紐づけます。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ViewModelオブジェクトを生成する
        val viewModelFactory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()
        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        val viewModel: MainViewModel = viewModelProvider.get(MainViewModel::class.java)
        // 上記3行のコードは、build.gradle(app)にimplementation 'android.arch.lifecycle:extensions:1.1.1'を追加すると下記で代替できるようになる
        // val viewModel: MainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

        // Bindingオブジェクトを生成する
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        // 色々紐付ける
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }
}

完成!

おめでとうございます、完成です!起動して触ってみましょう!
画面を回転してもフラグの状態が維持されていることがわかりますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Kotlin+DataBinding+ViewModelでとりあえず動くモノを作るための手続き

概要

AndroidのDataBindingやViewModelの実装について手続き的な面でつまづいたことがあったので自分なりにまとめました!
「とにかく動かすための手続きが知りたい」という方に向けてサンプルコードを具体的に記述します。

作るもの

ボタンのタップによって画像の表示⇔非表示がトグルするやつを作ります!

作りましょう

プロジェクトを作ろう

今回はこれでいきます!
ファイル名

登場人物

プロジェクトを作ったらこの人達をいじっていきます!

  • build.gradle(app)
  • MainViewModel.kt
  • MainActivity.kt
  • layout/activity_main.xml

build.gradle(app)

dataBinding{ enabled = true } をいい感じの場所に追加します!

MainViewModel.kt

android.arch.lifecycle.ViewModelを継承したMainViewModelを新規作成します!
こいつにデータ(今回だとフラグ)を保持させます。
toggle()が呼び出される度に_flag.valueがtrue⇔falseと切り替わるイメージです。

MainViewModel.kt
class MainViewModel : ViewModel() {
    private val _flag = MutableLiveData<Boolean>().apply {
        value = false
    }

    val flag: LiveData<Boolean>
        get() = _flag

    fun toggle() {
        _flag.postValue(!(flag.value ?: false))
    }
}

activity_main.xml

レイアウトファイルと、ついでにstringsファイルを弄ります!
バインディングされたオブジェクトのgetterは勝手に剥いてくれるので、三項演算子の部分はviewModel.flagと書いています。
viewModel.getFlag().getValue()でも動きます)

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
                name="viewModel"
                type="com.rodentia6.sampledatabinding.MainViewModel"/>
        <import type="android.view.View"/>
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

        <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="@{viewModel.flag ? View.VISIBLE : View.GONE}"
                app:srcCompat="@android:drawable/star_on"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintBottom_toTopOf="@id/button"
                app:layout_constraintLeft_toLeftOf="parent"
                android:contentDescription="@null"/>

        <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="80dp"
                android:text="@{viewModel.flag ? @string/kesu : @string/tukeru}"
                android:onClick="@{(v) -> viewModel.toggle()}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

    </android.support.constraint.ConstraintLayout>
</layout>
values/strings.xml
<resources>
    <string name="app_name">SampleDataBinding</string>
    <string name="tukeru">つける</string>
    <string name="kesu">けす</string>
</resources>

一旦リビルドをかける

DataBindingのクラスを自動生成してもらうために一旦リビルトします!(上メニューから Build > Rebuild Project)
何かが間違っていた場合はこの時点でエラーが発生します。

MainActivity.kt

Activityのクラスを編集します!
ViewModelオブジェクトとDataBindingオブジェクトを作成して色々紐づけます。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ViewModelオブジェクトを生成する
        val viewModelFactory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()
        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        val viewModel: MainViewModel = viewModelProvider.get(MainViewModel::class.java)
        // 上記3行のコードは、build.gradle(app)にimplementation 'android.arch.lifecycle:extensions:1.1.1'を追加すると下記で代替できるようになる
        // val viewModel: MainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

        // Bindingオブジェクトを生成する
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        // 色々紐付ける
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }
}

完成!

おめでとうございます、完成です!起動して触ってみましょう!
画面を回転してもフラグの状態が維持されていることがわかりますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LiveData + Coroutine + RetrofitでAPI呼び出しを簡潔に実装する

はじめに

LivedataがCoroutineに対応したので、
これらを利用してretrofitを用いたAPIの呼び出しを書きました。

使用ライブラリ

app.gradle
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha02"
implementation "com.squareup.retrofit2:retrofit:2.6.0"

実装

GitHubのAPIを用いてユーザー情報を取得するサンプルです。
ModelやDIの実装については割愛させていただきます。

API定義

GithubApi.kt
interface GithubApi {
  @GET("/users/{user}")
  fun getUser(@Path("user") loginName: String): LiveData<GithubUser>
}

Retrofitを用いたAPI呼び出しの定義部分。返り値の型をLiveDataにしています。

API呼び出し

GithubRepository.kt
class GithubRepository(val githubApi: GithubApi) {
  fun getUser(loginName: String) = liveData {
    emitSource(githubApi.getUser(loginName))
  }
}

上で定義したAPIの呼び出しです。
liveDataブロック内で受け取るliveDataを処理します。
emitSource()するとliveDataに値が流れ込みます。

LiveDataの取得

GithubViewModel.kt
class GithubViewModel(application: Application, private val repository: GithubRepository) : AndroidViewModel(application) {
  fun getUser(loginName: String) = repository.getUser(loginName)
}

あとはViewModelやFragmentなどでLiveDataとして値を取得できるので、
observeしたりdatabindingに値をセットしたりして利用します。

以上、Coroutine対応したLiveDataを用いてAPI呼び出しを実装する例を書きました。

参考

Lifecycle | Android Developers

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutterウィークリー #66

Flutterウィークリーとは?

FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/

この記事は#66の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-66

※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。

読み物&チュートリアル

#Hack19 - インターナショナルFlutterハッカソン結果

https://flutterhackathon.com/


これで、 Flutter Hackathon Webサイトのすべての参加者を確認できます。また、参加した場合は、証明書を取り戻すことができます。

FlutterとGoogle Cloud Platformが「学習」問題を解決するためにどのように連携したか

https://blog.greenroots.info/how-flutter-and-google-cloud-platform-came-together-to-solve-a-learning-problem-cjxj9uwng001i27s14hf61teg


Tapas Adhikaryが彼女の娘がFlutterとGoogleクラウドを使用してライティングスキルを向上させるのに役立った経緯。

Flutterスクロールアニメーションを作成する

https://itnext.io/creating-scroll-animations-in-flutter-74eb340627d9


Kenneth Reillyによるチュートリアルで、リストスクロールによるアニメーションの調整がいかに簡単かを示しています。

プロバイダを使用した下部ナビゲーションバー

https://medium.com/flutterdevs/bottom-navigation-bar-using-provider-flutter-8b607beb2e5a


Ashish Rawatが、BottomNavigationBarでFlutter Providerパッケージを使用する方法を説明します。

カスタムクリッパー:ウェーブカーブ - パート2

https://medium.com/@bhoomiharkhani22/custom-clipper-wave-curve-part-2-26cd54dc30f


Bhoomika HarkhaniはFlutter使ってクレイジーカーブを作り続けています

Flutterカスタムスティッキヘッダ

https://medium.com/flutter-community/custom-sticky-headers-in-flutter-248d3c26863


Dane Mackierのこの記事でスティッキヘッダーを使用する方法を学んでください。

ディープリンクとFlutterアプリケーションそれらを適切に処理する方法。

https://medium.com/@denisov.shureg/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283


Aleksandr Denisovが、 Flutter AndroidとiOSからのディープリンクを処理する方法についてこのチュートリアルを書きました。

Flutterアニメーション例 - #2

https://medium.com/flutterdevs/example-animations-in-flutter-2-1034a52f795b


これらの基本的なアニメーションの例を使って、Mohak Guptaによるアニメーションの学習を続けてください。

指紋と顔認識によるローカル認証

https://medium.com/flutter-community/flutter-local-authentication-with-fingerprint-and-face-recognition-744d5d7f6c4


Daniele Cambiによるこのチュートリアルでは、データを保護するために指紋と顔認識を使用する方法を学びます。

Flutter + MobX +チョッパー

https://medium.com/@rishabhv1509/flutter-mobx-chopper-8a1b0d7954a5


Rishabh Vermaによるアプリケーションで、Chopper(残りのクライアントライブラリ)とMobX(状態管理ライブラリ)を一緒に使用する。

Flutter学習パス

https://medium.com/@kingwu/flutter-learning-path-d6b3b0235799


Wu Chun WaはFlutterための彼の学習経路を共有しています。

Flutter :ステートフルウィジェットとステートレスウィジェット

https://medium.com/flutter-community/flutter-stateful-vs-stateless-db325309deae


Souvik Biswasは、ステートフルウィジェットとステートレスウィジェットの違いを説明します。

Flutter :開発における自己署名SSL証明書の使用

https://medium.com/@reme.lehane/flutter-using-self-signed-ssl-certificates-in-development-c3fe2d104acf


Reme Le Haneは、 FlutterカスタムSSL証明書を使用する際に問題がありました。

Flutter Proのようにネットワーク通話を処理する

https://medium.com/@xsahil03x/handling-network-calls-like-a-pro-in-flutter-31bd30c86be1


Sahil Kumarによる、すべてのネットワーク通話を処理するためのアプリの構成方法に関する素晴らしい記事。

Flutterペイントする方法

https://medium.com/@studymongolian/how-to-paint-in-flutter-d18c6c26df10


Flutterのカスタムペインターの紹介。

Flutter開発時間を毎日10%節約するにはどうすればいいですか?

https://medium.com/flutter-community/how-to-save-10-of-flutter-development-time-every-day-40c39833d1cb


自動テストでCI / CDを使用することで、どのように時間が短縮され、より良い品質が得られるかを学ぶための数字をご覧ください。

RouteObserverを使ってFlutter画面遷移を追跡する方法

https://medium.com/flutter-community/how-to-track-screen-transitions-in-flutter-with-routeobserver-733984a90dea


David AnayaがFlutter RouteObserverとRouteAwareを使って自動的に画面遷移を追跡する方法を説明します。

Flutter電池残量を確認する

https://medium.com/flutter-community/provide-battery-level-in-flutter-1b69fc4e6b2d


Dane Mackierは、バッテリーパーセンテージ値のストリームを提供するDeviceInformationSerivceを構築します。

Flutterを学ぶためのリソース

https://medium.com/flutter-community/resources-to-learn-flutter-2ade7aa73305


LaraMartínは、 Flutterを学ぶのに最適なリソースのリストをまとめました。

クロスプラットフォームのランディングページでのFlutterテーマの使用(Web、Android、iOS)

https://medium.com/flutter-community/using-flutter-themes-for-cross-platform-landing-page-web-android-ios-a6714c7aca99


Priyanka Tyagiによるこの記事でコードの一部を変更するだけで、ランディングページの外観やテーマをすばやく変更する方法を学びましょう。

ビデオ&メディア

Flutterカスタムリストビューカードを作成する方法

https://www.youtube.com/watch?v=1Y6o1DOUJcU


このビデオでは、カード用のカスタムウィジェットを作成し、それをカードのリストに使用する方法を紹介します。

Dartストリーム - フォーカスのFlutter

https://www.youtube.com/watch?v=nQBpOIHE4eE&list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2&index=16&linkId=69786502


このビデオでは、 Flutterストリームを使用したリアクティブコーディングの基本について説明します。

Flutter Flare Basics - GiphyのNavメニューを作ろう

https://www.youtube.com/watch?v=hwBUU9CP4qI&feature=youtu.be


Giphyモバイルアプリから素晴らしいナビゲーションメニューを再作成することで、 Flutter Flare 2Dでベクトルアニメーションの基本をマスターしましょう。

プレースホルダー(今週のFlutterウィジェット)

https://www.youtube.com/watch?v=LPe56fezmoo


作曲が終わっていないウィジェットの代わりになるものが必要ですか。プレースホルダーはここにあります。

プロバイダを用いた実用的な状態管理(The Boring Flutter Development Show、Ep。25)

https://www.youtube.com/watch?v=HrBiNHEqSYU&feature=youtu.be&linkId=69927151


このエピソードでは、FilipとMattがProviderパッケージをFlutterます。これは、 Flutterアプリで状態を管理するための簡単な方法です。

ライブラリ&コード

AmitJoki /エニグマ

https://github.com/AmitJoki/Enigma

Flutter完全に開発された本格的な一対一のチャットアプリ。

codemissions /フラッタービデオストリーミングアプリ

https://github.com/codemissions/flutter-video-streaming-app


flutterでビデオストリーミングアプリを構築する方法を示すチュートリアルのコード

JideGuru / Flutterトラベル

https://github.com/JideGuru/FlutterTravel


UpLabsからの旅行アプリのコンセプトのFlutter表現。

leisim / logger

https://github.com/leisim/logger#log-console


美しいログを印刷する小型で使いやすく拡張可能なロガー。

nickwu241 / instagroot

https://github.com/nickwu241/instagroot

グルートのためのInstagramのクローンを使用して作成したFlutter !

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ByteBufferの複製には、バイトオーダーの再設定が必要

バイト列を扱う際、バイトオーダーを意識しないといけない場合があります。

このような場合、Javaでは、ByteBufferクラスを使用すると便利です。ByteBufferにはバイトオーダーを設定でき、複数バイトから構成される値を簡単に扱えるためです。例えば、次のコードでは、リトルエンディアンの値を扱っています。

LittleEndianSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class LittleEndianSample {
    public static final void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);

        // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
        buffer.order(ByteOrder.LITTLE_ENDIAN)
            .put((byte) 0x11)
            .put((byte) 0x22)
            .rewind();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}

実行結果は次の通りです。

0x2211

ところが、上記のByteBufferを複製すると、バイトオーダーが想定外のものになってしまいます。例えば、リトルエンディアンのByteBufferを返すメソッドを書いているとします。その呼び出し元がByteBufferの内容を変更できないように、読み取り専用で返すことにします。しかし、次のようなコードでは、呼び出し元がビッグエンディアンのByteBufferを受け取ってしまいます。

BadSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class BadSample {
    private static class BufferObject {
        private final ByteBuffer buffer;

        public BufferObject() {
            buffer = ByteBuffer.allocate(Short.BYTES);

            // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
            buffer.order(ByteOrder.LITTLE_ENDIAN)
                .put((byte) 0x11)
                .put((byte) 0x22)
                .rewind();
        }

        private ByteBuffer getBuffer() {
            // 読み取り専用で複製(誤り。ビッグエンディアンになる)
            return buffer.asReadOnlyBuffer();
        }
    }

    public static final void main(String[] args) {
        BufferObject bufferObject = new BufferObject();

        ByteBuffer buffer = bufferObject.getBuffer();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}

実行結果は次のようになります。上位バイトと下位バイトとが入れ替わり、ビッグエンディアンになっていることがわかります。

0x1122

なぜこのような挙動となるのでしょうか。

OpenJDKのIssue Trackerに投稿されたIssue JDK-7178109によると、この挙動は仕様とのことです。

新規に作成されたByteBufferのバイトオーダーは、常にビッグエンディアンとなります。つまり、allocate()やallocateDirect()以外にも、下記のメソッドから返されるByteBufferのバイトオーダーは、元のByteBufferのバイトオーダーにかかわらず、常にビッグエンディアンです。

  • duplicate()
  • asReadOnlyBuffer()
  • wrap()
  • slice()
  • alignedSlice()

Java SEでは、バージョン9のAPIドキュメントから、上記の各メソッドに、作成されたByteBufferのバイトオーダーがビッグエンディアンになることが記されています。しかし、AndroidのAPIドキュメントでは、現時点(2019/7/9)ではそのような記述はありません。

以上から、バイトオーダーを保ったままByteBufferを複製するには、複製後にorder()でバイトオーダーを再度設定する必要があります。

DuplicationSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class DuplicationSample {
    private static class BufferObject {
        private final ByteBuffer buffer;

        public BufferObject() {
            buffer = ByteBuffer.allocate(Short.BYTES);

            // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
            buffer.order(ByteOrder.LITTLE_ENDIAN)
                .put((byte) 0x11)
                .put((byte) 0x22)
                .rewind();
        }

        private ByteBuffer getBuffer() {
            // 読み取り専用で複製。バイトオーダーを設定する必要がある。
            return buffer.asReadOnlyBuffer().order(buffer.order());
        }
    }

    public static final void main(String[] args) {
        BufferObject bufferObject = new BufferObject();

        ByteBuffer buffer = bufferObject.getBuffer();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}

実行結果は次の通りです。バイトオーダーが正しくリトルエンディアンになりました。

0x2211
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter で MLKit のカスタムモデルを使う

Flutter ではバッテリー、カメラ、イメージピッカーなど、React Native ではサードパーティから提供されているようなプラグインが公式で多数提供されています。公式で提供されているプラグインは flutter/plugins というリポジトリで開発されていますが、README を見るとその充実ぶりが分かるかと思います。公式から提供されていることによって、似たようなサードパーティのプラグインが乱立することが避けられていると思います。

残念なのは、細かいところに微妙に手が届いていないプラグインがあるということです。さらに悪いことに、Flutter の開発者がまだ多くないということが原因で、サードパーティもまだあまり充実していません。今回は MLKit for Firebase のカスタムモデルを Flutter から利用した例を紹介します。

使用したライブラリ

公式からは firebase_ml_vision というライブラリが提供されています。Firebase から提供されている機械学習モデルをそのまま利用する場合はこれでよいのですが、残念ながらカスタムモデルを使いたい場合には対応していません。

サードパーティのライブラリとしては flutter_mlkit というものがあります。このライブラリはカスタムモデルに対応しているので、これを使います。今回は v0.13.1 ベースのバージョンを使いました。カスタムモデルによっては複数入力、複数出力となることもありこれまで対応していなかったのですが、最近のプルリクエスト で複数入力、複数出力にも対応しています。ちなみに複数入力の扱いにバグを見つけたので プルリクエストを送っているところ です。

Flutter から Firebase を使う共通設定を行う

カスタムモデルを使う場合に限らずいつも必要な設定として Flutter から Firebase を使うため共通設定が必要です。以下の手順に従いました。

Flutter アプリに Firebase を追加する  |  Firebase

プロジェクト内の変更としては以下のものが必要になります。

  • com.google.gms:google-services を依存関係に追加(4.2.0 を使いました)
  • ↑ を Gradle プラグインとして有効化 (apply plugin: 'com.google.gms.google-services')

AndroidX を有効化する

flutter_mlkit のほうで AndroidX の機能を使っている箇所があります。具体的には以下のものです。

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

というわけで、利用している側でも設定を有効にしなければビルドできません。プロジェクトレベルの gradle.properties に以下の設定を追加しました。

 org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true

公式の情報としては以下を参照してください。

https://developer.android.com/jetpack/androidx?hl=ja#using_androidx

flutter_mlkit にパッチをあてる

複数入力の扱いにバグがあったので修正しました。本家のほうが治っていなければ以下のパッチをあててください。

https://github.com/azihsoyn/flutter_mlkit/pull/71

サンプルコード

2×5 の大きさの行列の和を計算するカスタムモデルを提供いただいたので、以下のようなサンプルコードを作成しました。

  • initTflite() ではカスタムモデルの読み込みを行っています。実際に処理を実行する直前までモデルのダウンロードを遅延させたいと思って _onActionButtonPressed() の中で await initTflite() のように書いていたのですが、なぜかモデルがダウンロードできませんでした。flutter_mlkit の中で非同期処理にバグがあるのではないかと疑っています。
  • _onActionButtonPressed() の中でモデルの計算処理を行っています。FirebaseModelInputOutputOptions は最近のアップデートで複数入力、複数出力に対応しました。
  • Firebase ML とのデータのやりとりは Uint8List を使って行います。残念なことに、多次元配列を1次元にうまくパックしてやる必要があるだけでなく、複数入力の場合には連結して渡してやるといった処理が必要になります。
  • Float32List から Uint8List への変換でバイトオーダーなどを気にしないといけないかと不安になりますが、ここについては多少マシで Float32List#buffer.asUint8List() というメソッドを使うことができます。
  • FirebaseModelInterpreter#run() の戻り値がかなり厄介で List<dynamic> となっています。実態としては 2×5 の大きさの行列が1つ出力されるので List<List<List<double>>> のようなものがなのですが、これがなぜかキャストできませんでした。これについては Dart の型システムを理解して出直して来たいと思っています。
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:mlkit/mlkit.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tflite add',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _left = "";
  String _right = "";
  String _output = "";
  FirebaseModelInterpreter interpreter = FirebaseModelInterpreter.instance;
  FirebaseModelManager manager = FirebaseModelManager.instance;

  Future<void> initTflite() {
    return this.manager.registerRemoteModelSource(
          FirebaseRemoteModelSource(
            modelName: "add",
            enableModelUpdates: true,
          ),
        );
  }

  void _onLeftOpChanged(String op) {
    setState(() {
      _left = op;
    });
  }

  void _onRightOpChanged(String op) {
    setState(() {
      _right = op;
    });
  }

  @override
  void initState() {
    super.initState();
    initTflite();
  }

  void _onActionButtonPressed() async {
    final leftNum = double.parse(_left);
    final rightNum = double.parse(_right);

    const ROW = 2;
    const COLUMN = 5;

    const options = FirebaseModelInputOutputOptions([
      FirebaseModelIOOption(FirebaseModelDataType.FLOAT32, [ROW, COLUMN]),
      FirebaseModelIOOption(FirebaseModelDataType.FLOAT32, [ROW, COLUMN]),
    ], [
      FirebaseModelIOOption(FirebaseModelDataType.FLOAT32, [ROW, COLUMN]),
    ]);

    final left = List<double>(ROW * COLUMN);
    final right = List<double>(ROW * COLUMN);
    for (var i = 0; i < ROW; i++) {
      for (var j = 0; j < COLUMN; j++) {
        left[COLUMN * i + j] = leftNum;
        right[COLUMN * i + j] = rightNum;
      }
    }
    final concat = <double>[]..addAll(left)..addAll(right);
    print(concat);
    final input = float32ListToUint8List(Float32List.fromList(concat));
    print(input);

    final output = await this.interpreter.run("add", options, input);
    print(output);

    setState(() {
      _output = (output[0][0][0] as double).toStringAsFixed(2);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextField(
                decoration: InputDecoration(
                  labelText: 'left-hand operand',
                ),
                onChanged: _onLeftOpChanged,
                keyboardType: TextInputType.numberWithOptions(
                  signed: true,
                  decimal: true,
                ),
              ),
              TextField(
                decoration: InputDecoration(
                  labelText: 'right-hand operand',
                ),
                onChanged: _onRightOpChanged,
                keyboardType: TextInputType.numberWithOptions(
                  signed: true,
                  decimal: true,
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(top: 32.0),
                child: Text(
                  _output,
                  style: Theme.of(context).textTheme.display1,
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _onActionButtonPressed,
        tooltip: 'Execute',
        child: Icon(Icons.add),
      ),
    );
  }
}

Uint8List float32ListToUint8List(Float32List list) {
  return list.buffer.asUint8List();
}

結果

Tensorflow Lite のカスタムモデルを使って計算する簡単なデモができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む