- 投稿日:2020-01-21T23:04:35+09:00
[Android]Navigation でどこまで戻るか制御する (Pop To と Inclusive)
はじめに
Navigation
ではどこまで戻るかをPop To
とInclusive
にて制御できます。
今回は実際にサンプルアプリを作成して、どこまで戻るか制御してみたいと思います。
用語 説明 Pop To Back
ボタンを押したとき、どこの画面まで戻るか指定できるInclusive Pop Up
で指定した画面の1つ前に戻るようにする準備
Navigation
をセットアップする
Navigation
を利用したアプリケーションを作成するための下準備をします。
次の手順で必要なライブラリやクラス、レイアウトを生成していきます。build.gradle(app)dependencies { def nav_version = "2.1.0" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" }
Fragment
を作成する
Navigation
を使った遷移で利用するFragment
を作成していきます。
次の手順でOneFragment
,TwoFragment
,ThreeFragment
の 3つを作成します。
Package Name
を右クリックし、New → Fragment → Fragment(Blank)
を選択するFragment Name
に任意の名称
を入力しOK
を押すFragment
にいらない記述が書かれているので消すLayout
にFragment
に遷移させるためのButton
を追加するLayout
にFragment
を遷移させたときのパラメータを表示されるTextView
を追加するFragment.ktclass OneFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_one, container, false) } }layout.xml<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".OneFragment"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="50dp"/> <Button android:layout_width="match_parent" android:layout_height="50dp" android:layout_gravity="bottom" android:text="Next" /> </FrameLayout>実装
次の手順で実装し、
Pop To
とInclusive
の動きを確認して見たいと思います。
No 名称 1 Navigation
で画面遷移させる2 Pop To を利用して指定した画面まで戻る 3 Inclusive で Pop To で指定した画面の1つ前に戻る Step1
Navigation
で画面遷移させる
Safe Args
でパラメータを引き渡すため基本となる画面遷移を実装していきます。Nav Graph の作成する
nav_graph.xml
を作成し、次の内容のNavigation Editor
で記述します。
Nav Graph
の作成の詳しい方法はこちらに書いてあるので参考にしてください。nav_graph.xml<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph" app:startDestination="@id/oneFragment"> <fragment android:id="@+id/oneFragment" android:name="c.kaleidot725.safeargssample.OneFragment" android:label="fragment_one" tools:layout="@layout/fragment_one" > <action android:id="@+id/action_oneFragment_to_twoFragment" app:destination="@id/twoFragment" /> </fragment> <fragment android:id="@+id/twoFragment" android:name="c.kaleidot725.safeargssample.TwoFragment" android:label="fragment_two" tools:layout="@layout/fragment_two" > <action android:id="@+id/action_twoFragment_to_threeFragment" app:destination="@id/threeFragment" /> </fragment> <fragment android:id="@+id/threeFragment" android:name="c.kaleidot725.safeargssample.ThreeFragment" android:label="fragment_three" tools:layout="@layout/fragment_three" /> </navigation>
Nav Host
を作成する
Main Activity
にFragment
を表示するためのNav Host
を作成します。activity_main.xml<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <fragment android:id="@+id/nav_host_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" /> </FrameLayout>
Nav Controller
を取得して画面を遷移させる
OneFragment
,TwoFragment
,ThreeFragment
で
Nav Controller
を取得して、Button
で画面遷移できるようにします。OneFragment.ktclass OneFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_one, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val b = view.findViewById<Button>(R.id.next_button) b.setOnClickListener { this.findNavController().navigate(R.id.action_oneFragment_to_twoFragment) } } }いまのところ、こんな感じで動作します。
Step2 Pop To を利用して指定した画面まで戻る
Navigation
のPop To
を利用するとどこ画面まで戻ることか指定できます。
通常はOneFragment
→TwoFragment
→ThreeFragment
と遷移しBack
ボタンを押すとTwoFragment
に戻ります。
TwoFragment
→ThreeFragment
の遷移にPop To
をOneFramgnet
に設定してみます。
OneFragment
→TwoFragment
→ThreeFragment
と遷移しBack
ボタンを押すと
OneFragment
に戻るようになります。というようにPop To
で指定したところまで戻ってくれるようになります。Step3 Inclusive で Pop To で指定した画面の1つ前に戻る
Navigation
のInclusive
を利用すると、Pop To
で指定した画面の1つに戻るようになります。
通常はOneFragment
→TwoFragment
→ThreeFragment
と遷移しBack
ボタンを押すとTwoFragment
に戻ります。
TwoFragment
→ThreeFragment
の遷移にPop To
をOneFramgnet
にしInclusive
にチェックを入れます。
OneFragment
→TwoFragment
→ThreeFragment
と遷移しBack
ボタンを押すと
OneFragment
の1つ前の画面に戻るのでアプリケーションが終了します。
Inclusive
ですがPop To
を指定しない場合だと機能しませんので注意が必要です。おわりに
Pop To
とInclusive
が詳しく解説されているところが、
少ないので理解しづらいかと思いますがわかれば簡単ですね。参考記事
- Navigationの戻る制御
- すごくわかりやすいです、本記事はこの記事の補足的な立ち位置で作成しました。
- 投稿日:2020-01-21T18:55:57+09:00
libtorch を Android でビルドするメモ
https://pytorch.org/mobile/android/#building-pytorch-android-from-source
とりあえず一式ビルドするスクリプトあります(実態は cmake を読んでいる)
setup
- Android SDK
- Android NDK
- cmake, ninja
- Gradle
が必要です. Ubuntu 18.04 で Android Studio をインストールしておくのが楽でしょうか.
export ANDROID_HOME=$HOME/Android/Sdk export ANDROID_NDK=$HOME/Android/Sdk/ndk/21.0.6113669/ # export GRADLE_HOME=$HOME/local/gradlegradle は
GRADLE_HOME
,GRADLE_PATH
で設定はうまく行かないかもです. PATH で直接gradle
が見えるようにしておくのがよさそうです.また, gradle は特定のバージョンでないとうまくいきません. 執筆時点では 4.10.3 でした. sdkman あたりでインストールしておきましょう. 使っている gradle version は
.circleci
あたりの設定ファイルで確認しましょう.あとは, python で yaml を使っているので
pip install pyyaml
しておきます.libtorch(.a) をビルドする
デフォルトでは arm, x86 でそれぞれ 32bit, 64bit で合計 4 種類ビルドするので, 必要な ABI が決まっていれば
ANDROID_ABI
で指定します.ANDROID_ABI=arm64-v8a BUILD_PYTORCH_MOBILE=1 ./scripts/build_android.shC++ アプリメインであれば, この .a をあとはリンクすればいけるかと思います.
Java binding やらをビルドして .so をビルドする
./scripts/build_pytorch_android.sh arm64-v8aなどとするだけです(内部で
build_android.sh
で .a を作ってから, fbjni などの .so を作っている).一部
c10/util/Registory.h
で cerr が見つからないと言われるかもしれません(.h で printf しているやんちゃなコードですね). そのときは適当にコメントアウトしてビルドしましょう.注意点
ANDROID_ABI など, 設定を変えたら
build_android*
あたりのフォルダを消して再度一からビルドする必要があります.TODO
- Debug, Release ビルドの切り替えを試す
- Custom build https://pytorch.org/mobile/android/#custom-build を試す.
- PC(Linux) 向けに libtorch ビルドする
tools/build_libtorch.py
でいけるようだが...
- 投稿日:2020-01-21T17:44:54+09:00
【和訳】Refactoring to Kotlin(Codelabs for the 2019 Android Dev Summit: Mountain View, October 23-24, 2019.)
1.ようこそ!
[このコードラボは、中国語とブラジル系ポルトガル語でも利用可能です]
このコードラボでは、JavaからKotlinにリファクタリングする方法を学びます。また、Kotlin言語の規則とあなたが書いているコードがそれらに従うことを保証する方法についても学びます。
このコードラボは、プロジェクトをKotlinに移行することを検討しているJavaを使用している開発者に適しています。IDEを使用してKotlinに変換する事をいくつかのJavaクラスから始めます。次に、変換されたコードを見て、より慣用的にし、一般的な落とし穴を避けることでコードを改善する方法を確認します。
学ぶこと
JavaをKotlinにリファクタリングする方法を学習します。 そうすることで、次のKotlin言語の機能と概念を学習します。
- nullabilityの処理
- シングルトンの実装
- データクラス
- 文字列の処理
- エルビス演算子
- Destructuring
- プロパティとバッキングプロパティ
- デフォルトの引数と名前付きパラメーター
- コレクションを操作する
- 拡張関数
- ret, apply, with, run キーワード
想定
すでにJavaに精通している必要があります。
必要なもの
Android Studio 3.5またはIntelliJ IDEA
Android Studio 3.6または4.0自動コンバーターは、異なる結果を作成する場合があります。そのため、このコードラボの目的でAndroid Studio 3.5を使用していることを確認してください。
2.セットアップ
新しいプロジェクトを作成する
IntelliJ IDEAを使用している場合は、Kotlin / JVMで新しいJavaプロジェクトを作成します。
Android Studioを使用している場合は、アクティビティのない新しいプロジェクトを作成します。コード
User
モデルオブジェクトと、User
オブジェクトと連携してユーザーおよびフォーマットされたユーザー名のリストを公開するRepository
シングルトンクラスを作成します。
app.java/ の下にUser.java
という新しいファイルを作成し、次のコードを貼り付けます。public class User { private String firstName; private String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
Repository.java
という新しいファイルを作成し、次のコードを貼り付けます。import java.util.ArrayList; import java.util.List; public class Repository { private static Repository INSTANCE = null; private List<User> users = null; public static Repository getInstance() { if (INSTANCE == null) { synchronized (Repository.class) { if (INSTANCE == null) { INSTANCE = new Repository(); } } } return INSTANCE; } // keeping the constructor private to enforce the usage of getInstance private Repository() { User user1 = new User("Jane", ""); User user2 = new User("John", null); User user3 = new User("Anne", "Doe"); users = new ArrayList(); users.add(user1); users.add(user2); users.add(user3); } public List<User> getUsers() { return users; } public List<String> getFormattedUserNames() { List<String> userNames = new ArrayList<>(users.size()); for (User user : users) { String name; if (user.getLastName() != null) { if (user.getFirstName() != null) { name = user.getFirstName() + " " + user.getLastName(); } else { name = user.getLastName(); } } else if (user.getFirstName() != null) { name = user.getFirstName(); } else { name = "Unknown"; } userNames.add(name); } return userNames; } }3.nullability、val、var、およびdataクラスの宣言
IDEは、Javaコードを自動的にKotlinコードにリファクタリングするという非常に良い仕事をすることができますが、時には少し助けが必要です。最初にこれを行い、次にリファクタリングされたコードを調べて、この方法でリファクタリングされた方法と理由を理解します。
Android Studio 3.6または4.0自動コンバーターは、異なる結果を作成する場合があります。そのため、このコードラボの目的でAndroid Studio 3.5を使用していることを確認してください。
User.java
ファイルに移動して、それをKotlinに変換します。Menu bar -> Code -> Convert Java File to Kotlin File.
IDEが変換後に修正を求めるプロンプトを表示した場合、はいを押します。次のKotlinコードが表示されます。
class User(var firstName: String?, var lastName: String?)
User.java
がUser.kt
に名前が変更されたことに注意してください。 Kotlinファイルの拡張子は.ktです。プロのヒント:JavaコードをKotlinファイルに貼り付けると、IDEは貼り付けられたコードを自動的にKotlinに変換します。
Java
User
クラスには、firstName
とlastNameの2つのプロパティがありました。それぞれにゲッターメソッドとセッターメソッドがあり、その値を可変にします。可変変数に対するKotlinのキーワードは
varです。そのため、コンバーターはこれらの各プロパティに
varを使用します。Javaプロパティにゲッターのみがある場合、それらは不変であり、
val変数として宣言されていました。
valは、Javaの
final`キーワードに似ています。KotlinとJavaの主な違いの1つは、変数がnull値を受け入れることができるかどうかをKotlinが明示的に指定することです。これを行うには、型宣言に
「?」
を追加します。Java
User
プロパティはnull値を受け入れることができるため、それぞれString?
でnull可能としてマークされます。Javaメンバーにnon-nullアノテーションを付ける場合(org.jetbrains.annotations.NotNull
またはandroidx.annotation.NonNull
)、コンバーターはこれを認識し、Kotlinのフィールドもnon-nullにします。プロのヒント:Kotlinでは、可能な限り不変オブジェクトを使用する(つまり、varの代わりにvalを使用する)ことをお勧めします。ヌルアビリティを意味のあるものにし、具体的に処理したいものに努力する必要があります。
基本的なリファクタリングはすでに行われています。 しかし、これをもっと慣用的な方法で書くことができます。方法を見てみましょう。
データクラス
User
クラスはデータのみを保持します。 Kotlinには、このロールを持つクラスのキーワードであるdata
があります。このクラスをdata
クラスとしてマークすることにより、コンパイラは自動的にゲッターとセッターを作成します。また、equals()
、hashCode()
、およびtoString()
関数も派生します。
User
クラスにdata
キーワードを追加しましょう:data class User(var firstName: String?, var lastName: String?)Kotlinは、Javaと同様に、プライマリコンストラクターと1つ以上のセカンダリコンストラクターを持つことができます。上記の例の1つは、Userクラスのプライマリコンストラクターです。複数のコンストラクターを持つJavaクラスを変換する場合、コンバーターはKotlinでも複数のコンストラクターを自動的に作成します。それらは
constructor
キーワードを使用して定義されます。コンストラクターの詳細については、公式ドキュメントをご覧ください。
このクラスのインスタンスを作成する場合は、次のようにします。
val user1 = User("Jane", "Doe")等しいこと
Kotlinには2種類の平等があります。
- 構造的平等では
==
演算子を使用し、equals()
を呼び出して2つのインスタンスが等しいかどうかを判断します。- 参照等価では、
===
演算子を使用して、2つの参照が同じオブジェクトを指しているかどうかを確認します。データクラスのプライマリコンストラクターで定義されたプロパティは、構造的な同等性チェックに使用されます。
val user1 = User("Jane", "Doe") val user2 = User("Jane", "Doe") val structurallyEqual = user1 == user2 // true val referentiallyEqual = user1 === user2 // falseデータクラスの詳細については、公式ドキュメントをご覧ください。
4.デフォルト引数、名前付き引数
Kotlinでは、関数呼び出しの引数にデフォルト値を割り当てることができます。引数が省略された場合、デフォルト値が使用されます。Kotlinでは、コンストラクターも関数なので、デフォルトの引数を使用して、
lastName
のデフォルト値がnull
であることを指定できます。これを行うには、lastName
にnull
を割り当てます。data class User(var firstName: String?, var lastName: String? = null) // usage val jane = User ("Jane") // same as User("Jane", null) val joe = User ("John", "Doe")関数を呼び出すときに、関数パラメーターに名前を付けることができます。
val john = User (firstName = "John", lastName = "Doe")別のユースケースとして、
firstName
にはデフォルト値としてnull
があり、lastName
にはないとしましょう。この場合、デフォルトパラメータは、デフォルト値のないパラメータの前にあるため、名前付き引数で関数を呼び出す必要があります:data class User(var firstName: String? = null, var lastName: String?) // usage val jane = User (lastName = "Doe") // same as User(null, "Doe") val john = User ("John", "Doe")関数に複数のパラメーターがある場合は、名前付き引数を使用するとコードが読みやすくなるため、使用を検討してください。
5.オブジェクトの初期化、コンパニオンオブジェクト、およびシングルトン
先に進む前に、Userクラスがdataクラスであることを確認してください。
Repository
クラスをKotlinに変換しましょう。自動変換の結果は次のようになります。class Repository // keeping the constructor private to enforce the usage of getInstance private constructor() { private val users: MutableList<User>? = null val formattedUserNames: List<String> get() { val userNames = ArrayList<String>(users!!.size) for ((firstName, lastName) in users!!) { val name: String? if (lastName != null) { if (firstName != null) { name = "$firstName $lastName" } else { name = lastName } } else if (firstName != null) { name = firstName } else { name = "Unknown" } userNames.add(name) } return userNames } init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") users = ArrayList() users!!.add(user1) users!!.add(user2) users!!.add(user3) } fun getUsers(): List<User>? { return users } companion object { private var INSTANCE: Repository? = null val instance: Repository get() { if (INSTANCE == null) { synchronized(Repository::class.java) { if (INSTANCE == null) { INSTANCE = Repository() } } } return INSTANCE } } }自動コンバーターが何をしたのか見てみましょう:
init
ブロックが追加されました(Repository.kt#L33)static
フィールドは、companion object
ブロックの一部になりました(Repository.kt#L51)- オブジェクトは宣言時にインスタンス化されなかったため、
users
のリストはNULL可能です(Repository.kt#L9)getFormattedUserNames()
メソッドは、formattedUserNames
(Repository.kt#L11)というプロパティになりました。- ユーザーのリストに対する反復の構文は、Javaの構文とは異なります(Repository.kt#L14)
注:生成されたコードはコンパイルされません。 心配する必要はありません。次のステップで変更します。
初期化ブロック
Kotlinでは、プライマリコンストラクターにコードを含めることはできないため、初期化コードはinitブロックに配置されます。 機能は同じです。
class Repository private constructor() { ... init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") users = ArrayList() users!!.add(user1) users!!.add(user2) users!!.add(user3) } }
init
コードの多くは、プロパティの初期化を処理します。これは、プロパティの宣言でも実行できます。たとえば、Repository
クラスのKotlinバージョンでは、usersプロパティが宣言で初期化されたことがわかります。private val users: MutableList<User>? = null公式ドキュメントから初期化ブロックの詳細をご覧ください。
Kotlinの
static
プロパティとメソッドJavaでは、フィールドまたは関数に
static
キーワードを使用して、クラスのインスタンスではなくクラスに属していることを示します。これが、Repository
クラスにINSTANCE
静的フィールドを作成した理由です。これに対応するKotlinは、companion object
ブロックです。 ここでは、静的フィールドと静的関数も宣言します。コンバーターは、INSTANCE
フィールドを作成して、ここに移動しました。シングルトンの処理
Repository
クラスのインスタンスは1つしか必要ないため、Javaでシングルトンパターンを使用しました。Kotlinを使用すると、class
キーワードをobject
に置き換えることで、コンパイラレベルでこのパターンを適用できます。
プライベートコンストラクターとコンパニオンオブジェクトを削除し、クラス定義をobject Repository
に置き換えます。object Repository { private val users: MutableList<User>? = null val formattedUserNames: List<String> get() { val userNames = ArrayList<String>(users!!.size) for ((firstName, lastName) in users!!) { val name: String? if (lastName != null) { if (firstName != null) { name = "$firstName $lastName" } else { name = lastName } } else if (firstName != null) { name = firstName } else { name = "Unknown" } userNames.add(name) } return userNames } init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") users = ArrayList() users!!.add(user1) users!!.add(user2) users!!.add(user3) } fun getUsers(): List<User>? { return users } }
object
クラスを使用する場合、次のように、オブジェクトに対して関数とプロパティを直接呼び出します。val users = Repository.usersオブジェクトとコンパニオンオブジェクトの詳細については、公式ドキュメントをご覧ください。
Destructuring
Kotlinでは、構造化宣言と呼ばれる構文を使用して、オブジェクトをいくつかの変数に構造化できます。複数の変数を作成し、それらを個別に使用できます。
たとえば、dataクラスは構造化をサポートしているため、自動コンバーターはfor
ループでUser
オブジェクトを構造化しました。これにより、firstName
およびlastName
の値を直接操作できます。for ((firstName, lastName) in users!!) { val name: String? if (lastName != null) { if (firstName != null) { name = "$firstName $lastName" } ...公式ドキュメントで宣言のdestructuringについて詳しく読んでください。
6. nullabilityの処理
Repository
クラスをKotlinに変換する場合、自動コンバータは、宣言時にオブジェクトに初期化されなかったため、ユーザーのリストをnull可能にしました。users
オブジェクトのすべての使用法について、not-null アサーション演算子!!
が使用されている。変数をnull以外の型に変換し、値がnullの場合は例外をスローします。!!
を使用すると、実行時に例外がスローされる危険があります。
代わりに、次の方法のいずれかを使用してヌル可能性を処理することを好みます。
- nullチェックを行う(if(users!= null){...})
- elvis演算子の使用
?:
(コードラボで後述)- Kotlin標準関数の一部を使用(コードラボで後ほど説明)
公式ドキュメントからヌルの安全性に関する詳細をご覧ください。
この場合、ユーザーのリストはNULL値を許可する必要はありませんが、オブジェクトが構築された直後に初期化されるため、宣言時にオブジェクトを直接インスタンス化できます。
コレクションタイプのインスタンスを作成する場合、Kotlinはコードをより読みやすく柔軟にするためのいくつかのヘルパー関数を提供します。ここでは、users
にMutableList
を使用しています。private val users: MutableList<User>? = null簡単にするために、
mutableListOf()
関数を使用できます。リスト要素タイプを提供し、init
ブロックからArrayList
コンストラクター呼び出しを削除し、usersプロパティの明示的な型宣言を削除します。private val users = mutableListOf<User>()この変更により、
users
プロパティはnullでなくなり、!!
演算子の出現で不要なものをすべて削除できます。また、ユーザー変数は既に初期化されているため、init
ブロックから初期化を削除する必要があります。init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") users.add(user1) users.add(user2) users.add(user3) }
lastName
とfirstName
の両方がnull
になる可能性があるため、フォーマットされたユーザー名のリストを作成するときにnullを処理する必要があります。自動コンバーターは名前変数をnull可能にしましたが、いずれかの名前が欠落している場合は"Unknown"
を表示したいので、型宣言から?
を削除することで名前をnull以外にできます。val name: String
lastName
がnullの場合、name
はfirstName
または"Unknown"
のいずれかです。if (lastName != null) { if (firstName != null) { name = "$firstName $lastName" } else { name = lastName } } else if (firstName != null) { name = firstName } else { name = "Unknown" }これは、elvis演算子
?:
を使用して、より慣用的に記述することができます。elvis演算子は、nullでない場合は左辺の式を返し、左辺がnullの場合は右辺の式を返します。
したがって、次のコードでは、user.firstName
がnullでない場合に返されます。user.firstName
がnullの場合、式は右側の値"Unknown"
を返します。if (lastName != null) { ... } else { name = firstName ?: "Unknown" }公式ドキュメントでelvisオペレーターの詳細をお読みください。
7.文字列テンプレートとif式
Kotlinでは、String templatesを使用して文字列を簡単に操作できます。文字列テンプレートを使用すると、文字列宣言内の変数を参照できます。
自動コンバーターは、$
記号を使用してストリング内の変数名を直接参照し、式を{}
の間に置くことで、姓と名の連結を更新しました。// Java name = user.getFirstName() + " " + user.getLastName(); // Kotlin name = "${user.firstName} ${user.lastName}"コードで、文字列連結を次のように置き換えます。
name = "$firstName $lastName"Kotlinでは、
if
、when
、for
、while
が式であり、値を返します。IDEは、割り当てをif
から外す必要があるという警告も表示します。
IDEの提案に従って、両方のif
ステートメントの割り当てを解除しましょう。if
ステートメントの最後の行が割り当てられます。このように、このブロックの唯一の目的が名前の値を初期化することであることはより明確です。name = if (firstName != null) { // do something firstName } // name = firstName
if
ステートメントから割り当てを解除すると、簡単に悪用される可能性があります。if
ブロックに1つの役割のみがあり、他の副作用がないことを確認してください。次に、
name
宣言を割り当てと結合できるという警告が表示されます。 これも適用してみましょう。名前変数の型を推測できるため、明示的な型宣言を削除できます。次に、formattedUserNames
は次のようになります。val formattedUserNames: List<String> get() { val userNames = ArrayList<String>(users.size) for ((firstName, lastName) in users) { val name = if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName } } else { firstName ?: "Unknown" } userNames.add(name) } return userNames }公式ドキュメントで
if
、when
、for
、while
の詳細を読んでください。8.コレクションの操作
formattedUserNames
ゲッターを詳しく見て、それをもっと慣用的にする方法を見てみましょう。現在、コードは次のことを行います。
- 文字列の新しいリストを作成します
- ユーザーのリストを反復処理します
- ユーザーの名と姓に基づいて、各ユーザーの書式設定された名前を作成します
- 新しく作成されたリストを返します
val formattedUserNames: List<String> get() { val userNames = ArrayList<String>(users.size) for ((firstName, lastName) in users) { val name = if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName } } else { firstName ?: "Unknown" } userNames.add(name) } return userNames }Kotlinは、Java Collections APIの機能を拡張することにより、開発をより迅速かつ安全にするコレクション変換の広範なリストを提供します。それらの1つはマップ機能です。この関数は、指定された変換関数を元の配列の各要素に適用した結果を含む新しいリストを返します。そのため、新しいリストを作成してユーザーのリストを手動で繰り返す代わりに、
map
関数を使用して、forループ内のロジックをmap
本体内に移動できます。デフォルトでは、map
で使用される現在のリスト項目の名前はit
ですが、読みやすくするために、独自の変数名に置き換えることができます。この例では、user
に名前を付けましょう。val formattedUserNames: List<String> get() { return users.map { user -> val name = if (user.lastName != null) { if (user.firstName != null) { "${user.firstName} ${user.lastName}" } else { user.lastName ?: "Unknown" } } else { user.firstName ?: "Unknown" } name } }これをさらに単純化するために、
name
変数を完全に削除できます:val formattedUserNames: List<String> get() { return users.map { user -> if (user.lastName != null) { if (user.firstName != null) { "${user.firstName} ${user.lastName}" } else { user.lastName ?: "Unknown" } } else { user.firstName ?: "Unknown" } } }コレクションの変換だけでなく、Kotlin Standard Libraryはコレクションを管理するための幅広いツールを提供します。さまざまなコレクションタイプから、一般的な
Collection
タイプまたはList
やSet
などのさまざまなサブタイプに固有のさまざまな操作まで。一般に、コレクションを操作するときは、Kotlinでの書き込みを計画している機能が標準ライブラリによって既に実装されているかどうかを確認し、独自の実装でその機能を使用することを選択してください。9.プロパティとバッキングプロパティ
自動コンバーターが
getFormattedUserNames()
関数を、カスタムgetterを持つformattedUserNames
と呼ばれるプロパティに置き換えていることがわかりました。 内部では、KotlinはList
を返すgetFormattedUserNames()
メソッドを生成します。
Javaでは、getterおよびsetter関数を介してクラスプロパティを公開します。
Kotlinを使用すると、フィールドで表現されたクラスのプロパティと、機能で表現された機能、クラスで実行可能なアクションをより適切に区別できます。この場合、Repositoryクラスは非常にシンプルで、アクションを実行しないため、フィールドのみがあります。
formattedUserNames
Kotlinプロパティのgetterを呼び出すときにJavaでトリガーされたロジックのgetFormattedUserNames()
関数がトリガーされるようになりました。
formattedUserNames
プロパティに対応するフィールドは明示的にありませんが、
Kotlinは、必要に応じてカスタムgetterおよびsetterからアクセスできるfieldという名前の自動バッキングフィールドを提供します。
ただし、自動バッキングフィールドにはない追加機能が必要な場合があります。 以下の例を見てみましょう。
Repositoryクラス内には、Javaコードから生成されたgetUsers関数で公開されているユーザーの可変リストがあります。fun getUsers(): List<User>? { return users }ここでの問題は、ユーザーを返すことにより、Repositoryクラスのすべてのコンシューマーがユーザーのリストを変更できることです。 バッキングプロパティを使用してこれを修正しましょう。
まず、ユーザーの名前を_usersに変更しましょう。
次に、ユーザーのリストを返すパブリックの不変のプロパティを追加します。 それをユーザーと呼びましょう:private val _users = mutableListOf<User>() val users: List<User> get() = _usersこの変更により、プライベート
_users
プロパティがパブリックusers
プロパティのバッキングプロパティになります。Repository
クラスの外部では、_users
リストは変更できません。クラスのコンシューマーはusers
を介してのみリストにアクセスできるためです。バッキングプロパティの規則では、先頭にアンダースコアを使用します。
公式ドキュメントからプロパティの詳細をご覧ください。Full code:
object Repository { private val _users = mutableListOf<User>() val users: List<User> get() = _users val formattedUserNames: List<String> get() { return _users.map { user -> if (user.lastName != null) { if (user.firstName != null) { "${user.firstName} ${user.lastName}" } else { user.lastName ?: "Unknown" } } else { user.firstName ?: "Unknown" } } } init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") _users.add(user1) _users.add(user2) _users.add(user3) } }10.トップレベルおよび拡張機能とプロパティ
現在、
Repository
クラスは、User
オブジェクトのフォーマットされたユーザー名を計算する方法を知っています。ただし、他のクラスで同じフォーマットロジックを再利用する場合は、コピーして貼り付けるか、User
クラスに移動する必要があります。
Kotlinは、クラス、オブジェクト、またはインターフェースの外部で関数とプロパティを宣言する機能を提供します。たとえば、リストの新しいインスタンスを作成するために使用したmutableListOf()
関数は、標準ライブラリのCollections.ktで直接定義されます。
Javaでは、ユーティリティ機能が必要な場合はいつでも、Util
クラスを作成し、その機能を静的関数として宣言する可能性が高くなります。Kotlinでは、クラスを持たずにトップレベルの関数を宣言できます。Kotlinはまた、拡張機能を作成する機能も提供します。これらは特定の型を拡張する関数ですが、型の外部で宣言されています。そのため、それらはそのタイプに類似しています。クラスを所有していないか、継承に対して開かれていないため、クラスの機能を拡張するには、Kotlinは、拡張機能と呼ばれる特別な宣言を作成します。Kotlinは、拡張機能と拡張プロパティをサポートしています。
拡張機能とプロパティの可視性は、可視性修飾子を使用して制限できます。これらは、拡張を必要とするクラスにのみ使用を制限し、名前空間を汚染しません。
Userクラスの場合、フォーマットされた名前を計算する拡張関数を追加するか、フォーマットされた名前を拡張プロパティに保持できます。リポジトリクラスの外部の同じファイルに追加できます。
// extension function fun User.getFormattedName(): String { return if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName ?: "Unknown" } } else { firstName ?: "Unknown" } } // extension property val User.userFormattedName: String get() { return if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName ?: "Unknown" } } else { firstName ?: "Unknown" } } // usage: val user = User(...) val name = user.getFormattedName() val formattedName = user.userFormattedNameその後、拡張機能とプロパティを
User
クラスの一部であるかのように使用できます。
フォーマットされた名前はユーザーのプロパティであり、Repository
クラスの機能ではないため、拡張プロパティを使用しましょう。Repository
ファイルは次のようになります。val User.formattedName: String get() { return if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName ?: "Unknown" } } else { firstName ?: "Unknown" } } object Repository { private val _users = mutableListOf<User>() val users: List<User> get() = _users val formattedUserNames: List<String> get() { return _users.map { user -> user.formattedName } } init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") _users.add(user1) _users.add(user2) _users.add(user3) } }Kotlin標準ライブラリは、拡張関数を使用していくつかのJava APIの機能を拡張します。
Iterable
およびCollection
の多くの機能は、拡張機能として実装されています。たとえば、前の手順で使用したマップ関数は、Iterable
の拡張関数です。11.スコープ関数:let、apply、with、run、also
Repository
クラスコードでは、いくつかのユーザーオブジェクトを_users
リストに追加しています。これらの呼び出しは、スコープ関数の助けを借りてより慣用的にすることができます。
名前に基づいてオブジェクトにアクセスする必要なく、特定のオブジェクトのコンテキストでのみコードを実行するために、Kotlinは5つのスコープ関数let
、apply
、with
、run
、also
を作成しました。短く強力なこれらの関数はすべて、レシーバー(this
)を持ち、引数(it
)を持ち、値を返すことがあります。達成したい内容に応じて、どれを使用するかを決定します。ここからスコープ機能のチートシートをダウンロードしてください。
Repository
で_users
オブジェクトを構成しているため、apply
関数を使用してコードをより慣用的にすることができます。init { val user1 = User("Jane", "") val user2 = User("John", null) val user3 = User("Anne", "Doe") _users.apply { // this == _users add(user1) add(user2) add(user3) }公式ドキュメントからスコープ関数の詳細をご覧ください。
12.まとめ
このコードラボでは、JavaからKotlinへのコードのリファクタリングを開始するために必要な基本について説明しました。このリファクタリングは開発プラットフォームに依存せず、記述するコードが慣用的であることを保証するのに役立ちます。
慣用的なKotlinを使用すると、コードを短く簡潔に書くことができます。Kotlinが提供するすべての機能を使用すると、コードをより安全で簡潔で読みやすくするための方法がたくさんあります。たとえば、_users
リストを宣言で直接ユーザーでインスタンス化し、init
ブロックを削除することで、Repository
クラスを最適化することもできます。private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))nullability、シングルトン、文字列、コレクションの処理から、拡張機能、トップレベル機能、プロパティ、スコープ機能などのトピックまで、幅広いトピックを取り上げました。2つのJavaクラスから、次のような2つのKotlinクラスに移行しました。
User.ktclass User(var firstName: String?, var lastName: String?)Repository.ktval User.formattedName: String get() { return if (lastName != null) { if (firstName != null) { "$firstName $lastName" } else { lastName ?: "Unknown" } } else { firstName ?: "Unknown" } } object Repository { private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe")) val users: List<User> get() = _users val formattedUserNames: List<String> get() = _users.map { user -> user.formattedName } }Java機能のTL; DRと、Kotlinへのマッピングを次に示します。
Java Kotlin final object val object equals() == == === Class that just holds data data class Initialization in the constructor Initialization in the init block static fields and functions fields and functions declared in a companion object Singleton class object Kotlinの詳細とプラットフォームでのKotlinの使用方法については、次のリソースをご覧ください。
- Kotlin Koans
- Kotlin Tutorials
- Developing Android apps with Kotlin - free course
- Kotlin Bootcamp for Programmers
- https://www.coursera.org/learn/kotlin-for-java-developers - free course in Audit mode
- 投稿日:2020-01-21T17:25:56+09:00
桁区切り付きで数値を表示するExtension
java.text.NumberFormat
を使うだけです。// Int用 fun TextView.setNumberWithThousandsSeparator(number: Int, locale: Locale = Locale.JAPAN) { setNumberWithThousandsSeparator(number.toLong(), locale) } // Long用 fun TextView.setNumberWithThousandsSeparator(number: Long, locale: Locale = Locale.JAPAN) { text = NumberFormat.getNumberInstance(locale).format(number) } // Float用 fun TextView.setNumberWithThousandsSeparator(number: Float, locale: Locale = Locale.JAPAN) { setNumberWithThousandsSeparator(number.toLong(), locale) } // Double用 fun TextView.setNumberWithThousandsSeparator(number: Double, locale: Locale = Locale.JAPAN) { text = NumberFormat.getNumberInstance(locale).format(number) }
- 投稿日:2020-01-21T17:13:01+09:00
[Android] ProgressBarの使い方
使い方
Widget.ProgressBarを追加して、表示非表示を変えるだけで良い。
表示非表示の切替え
private ProgressBar progressBar; // 表示 progressBar.setVisibility(android.widget.ProgressBar.VISIBLE); // 非表示 progressBar.setVisibility(android.widget.ProgressBar.INVISIBLE);結果
困ったところ
前回ProgressDialogを扱ったが、https://qiita.com/QiitaD/items/dc6f823e722f5556030f
API非推奨になっていた。現在はProgressBarを使うらしい。見落とさないよう気を付けて頂きたい。感想
ProgressDialogよりも簡単に使えた。
参考URL
- 投稿日:2020-01-21T14:40:41+09:00
Retrofit2で共通エラー処理。ついでにローディングの判定
APIで共通のエラーレスポンス(400)などがあり、救う手段もないのでとりあえず「通信エラーが発生しました。」みたいなトーストを適当に出すことありますよね。
APIメソッド全てのステータスコードをハンドリングするのは面倒なのでどこかでまとめたいと思いました。
あまりアーキテクチャとかは意識してません構成
├── api │ ├── exampleService │ │ ├──ExampleApi │ │ └──ExampleService │ └── HttpManager │ └── ExampleActivityExampleActivity → ExampleApi → ExampleService → HttpManager
の順でAPIをコールしつつLiveDataを受け渡してHttpManager → ExampleActivity
のようにHttpManagerでパースしたエラーをActivityへLiveData使ってポストします。実装
HttpManager
open class HttpManager<T>(context: Context, serviceClass: Class<T>) { private val BASE_URL: String = context.getString(R.string.wallet_api_url) private var isLoading: MutableLiveData<Boolean>? = null private var error: MutableLiveData<ErrorResponse>? = null private val service = getRetrofit( serviceClass, BASE_URL, getHttpClient(context) ) fun init( isLoading: MutableLiveData<Boolean>? = null, error: MutableLiveData<ErrorResponse>? = null ):T { this.isLoading = isLoading this.error = error return service } private fun createHeader(context: Context, request: Request): Request { return request.newBuilder() .addHeader("Accept", "application/json") .build() } private fun getHttpClient(context: Context) : OkHttpClient { val interceptor = Interceptor { chain -> val response = chain.proceed(createHeader(context, chain.request())) if(response.code() != ErrorCode.HTTP_OK_200.httpErrorCode) { try { val source = response.body()?.source() source?.request(java.lang.Long.MAX_VALUE) val bodyString = source?.buffer?.clone()?.readString(Charset.forName("UTF-8")).toString() val errorBase = Gson().fromJson(bodyString, ErrorBase::class.java) error?.postValue(ErrorResponse(response.code(), errorBase)) } catch (e: Exception) { error?.postValue(ErrorResponse(response.code(), ErrorBase("Unknown Error", null))) } } response } return OkHttpClient.Builder() .addInterceptor(interceptor) .addInterceptor(getLoggingInterceptor()) .eventListener(object: EventListener(){ override fun callStart(call: Call) { isLoading?.postValue(true) super.callStart(call) } override fun callEnd(call: Call) { isLoading?.postValue(false) super.callEnd(call) } }) .readTimeout(5, TimeUnit.SECONDS) .connectTimeout(5, TimeUnit.SECONDS) .build() } private fun getLoggingInterceptor(): HttpLoggingInterceptor { val logging = HttpLoggingInterceptor() if (BuildConfig.DEBUG) { logging.level = HttpLoggingInterceptor.Level.BODY } else { logging.level = HttpLoggingInterceptor.Level.NONE } return logging } private fun getRetrofit(serviceClass: Class<T>, baseUrl: String, httpClient: OkHttpClient) : T { val retrofit: Retrofit = Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(getConverter()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(httpClient) .build() return retrofit.create(serviceClass) } private fun getConverter() : Converter.Factory { return GsonConverterFactory.create(GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create()) } }Interceptor使ってGsonでパースしています。
if(response.code() != ErrorCode.HTTP_OK_200.httpErrorCode) {
のところを 400,401,402~ みたいにハンドリングしていきます。(↑ではやってませんが)
パースでException起きがちなので雑にtry~catchします。
OkHttpClient.Builder()のリスナーでisLoadingをpostしています。ExampleApi, ExampleService
class ExampleApi(context: Context ) : HttpManager< ExampleApi. ExampleService>(context, ExampleService::class.java) { interface ExampleService { @POST("/user/setting") fun setting(): Call<ExampleResponse> } fun setting(responseLiveData: MutableLiveData<ExampleResponse>, isLoading: MutableLiveData<Boolean>?, error: MutableLiveData<ErrorResponse>?) { GlobalScope.launch { init(isLoading, error) .setting() .enqueue(object: Callback<ExampleResponse> { override fun onFailure(call: Call<ExampleResponse>, t: Throwable) { } override fun onResponse(call: Call<ExampleResponse>, response: retrofit2.Response<ExampleResponse>) { if (response.isSuccessful) { responseLiveData.postValue(response.body()) } } }) } } data class ExampleResponse( val hoge: Boolean, val fuga: String ) }settingをactivityから呼び出します。
レスポンスはすべてonResponseにきますが、エラーのハンドリングはHttpManagerでしているのでresponse.isSuccessful
の時のみresponseLiveDataでExampleResponseをポストします。Activity
val response = MutableLiveData<ExampleApi.ExampleResponse>() val error = MutableLiveData<ErrorResponse>() val isLoading = MutableLiveData<Boolean>() WalletUserApi(this).setting(response, isLoading, error) response.observeForever { result -> Toast.makeText(this, "response: $result", Toast.LENGTH_SHORT ).show() } error.observeForever { result -> Toast.makeText(this, "response: $result", Toast.LENGTH_SHORT ).show() } isLoading.observeForever { //プログレスダイアログ出したりDatabindingでいい感じにする }Activity側の実装がかなりスッキリしました。
LiveDataはオーバーライドしたApplicationクラスなどに持たせればどこからでもアクセスできるのでいい感じです。
- 投稿日:2020-01-21T12:25:45+09:00
Expo × React NativeアプリをAndroidで動かした時、TouchableOpacityのonPressが動かない
- 投稿日:2020-01-21T02:10:25+09:00
macOS Catalina + Unity 2019.3でAndroidビルド(IL2CPP)に失敗する場合の対処
概要
macOS Catalina + Unity 2019.3でAndroidビルド(IL2CPP)に失敗したので対処のメモを残しておく。
何が起きたか
IL2CPPでAndroid向けビルドをした時に、以下のエラーが発生。
Failed running /Applications/Unity/Hub/Editor/2019.3.0f5/Unity.app/Contents/il2cpp/build/deploy/il2cppcore/il2cppcore.dll (以下略)
原因(おそらく)
Android NDK を手動でインストールしたことにより、macOS Catalinaの強力すぎるセキュリティに引っかかってコマンドを実行できなかった。
対処方法
UnityHubからAndroid NDK をインストールする。(SDKも一緒にインストールされるが)
PreferenceからNDKのパスを設定することを忘れないこと。環境
Mac OS 10.15.2
Unity 2019.3.0f5