20200121のAndroidに関する記事は8件です。

[Android]Navigation でどこまで戻るか制御する (Pop To と Inclusive)

はじめに

Navigation ではどこまで戻るかをPop ToInclusive にて制御できます。
今回は実際にサンプルアプリを作成して、どこまで戻るか制御してみたいと思います。

用語  説明
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つを作成します。

  1. Package Name を右クリックし、New → Fragment → Fragment(Blank) を選択する
  2. Fragment Name任意の名称 を入力し OK を押す
  3. Fragment にいらない記述が書かれているので消す
  4. LayoutFragment に遷移させるための Button を追加する
  5. LayoutFragment を遷移させたときのパラメータを表示されるTextViewを追加する
Fragment.kt
class 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)
    }
}

image.png

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 ToInclusive の動きを確認して見たいと思います。

No 名称
1 Navigationで画面遷移させる
2 Pop To を利用して指定した画面まで戻る
3 Inclusive で Pop To で指定した画面の1つ前に戻る

Step1 Navigationで画面遷移させる

Safe Argsでパラメータを引き渡すため基本となる画面遷移を実装していきます。

Nav Graph の作成する

nav_graph.xmlを作成し、次の内容のNavigation Editorで記述します。
Nav Graphの作成の詳しい方法はこちらに書いてあるので参考にしてください。

image.png

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 ActivityFragmentを表示するための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.kt
class 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)
        }
    }
}

いまのところ、こんな感じで動作します。

image.png

Step2 Pop To を利用して指定した画面まで戻る

NavigationPop To を利用するとどこ画面まで戻ることか指定できます。
通常は OneFragmentTwoFragmentThreeFragmentと遷移し Backボタンを押すと TwoFragmentに戻ります。

image.png

image.png

TwoFragmentThreeFragmentの遷移にPop ToOneFramgnetに設定してみます。
OneFragmentTwoFragmentThreeFragmentと遷移しBackボタンを押すと
OneFragmentに戻るようになります。というようにPop To で指定したところまで戻ってくれるようになります。

image.png

image.png

Step3 Inclusive で Pop To で指定した画面の1つ前に戻る

NavigationInclusive を利用すると、Pop Toで指定した画面の1つに戻るようになります。
通常は OneFragmentTwoFragmentThreeFragmentと遷移し Backボタンを押すと TwoFragmentに戻ります。

image.png

image.png

TwoFragmentThreeFragmentの遷移にPop ToOneFramgnetにしInclusiveにチェックを入れます。
OneFragmentTwoFragmentThreeFragmentと遷移しBackボタンを押すと
OneFragmentの1つ前の画面に戻るのでアプリケーションが終了します。

image.png

image.png

Inclusive ですが Pop Toを指定しない場合だと機能しませんので注意が必要です。

おわりに

Pop ToInclusiveが詳しく解説されているところが、
少ないので理解しづらいかと思いますがわかれば簡単ですね。

参考記事

  • Navigationの戻る制御
    • すごくわかりやすいです、本記事はこの記事の補足的な立ち位置で作成しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/gradle

gradle は 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.sh

C++ アプリメインであれば, この .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

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

【和訳】Refactoring to Kotlin(Codelabs for the 2019 Android Dev Summit: Mountain View, October 23-24, 2019.)

Android Dev Summit 2019
Codelabs for the 2019 Android Dev Summit: Mountain View, October 23-24, 2019.

Refactoring to Kotlinの日本語訳

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が変換後に修正を求めるプロンプトを表示した場合、はいを押します。

image.png

次のKotlinコードが表示されます。

class User(var firstName: String?, var lastName: String?)

User.javaUser.ktに名前が変更されたことに注意してください。 Kotlinファイルの拡張子は.ktです。

プロのヒント:JavaコードをKotlinファイルに貼り付けると、IDEは貼り付けられたコードを自動的にKotlinに変換します。

Java Userクラスには、firstNamelastNameの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であることを指定できます。これを行うには、lastNamenullを割り当てます。

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はコードをより読みやすく柔軟にするためのいくつかのヘルパー関数を提供します。ここでは、usersMutableListを使用しています。

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)
}

lastNamefirstNameの両方がnullになる可能性があるため、フォーマットされたユーザー名のリストを作成するときにnullを処理する必要があります。自動コンバーターは名前変数をnull可能にしましたが、いずれかの名前が欠落している場合は"Unknown"を表示したいので、型宣言から?を削除することで名前をnull以外にできます。

val name: String

lastNameがnullの場合、namefirstNameまたは"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では、ifwhenforwhileが式であり、値を返します。IDEは、割り当てをifから外す必要があるという警告も表示します。
image.png
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
        }

公式ドキュメントifwhenforwhileの詳細を読んでください。

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タイプまたはListSetなどのさまざまなサブタイプに固有のさまざまな操作まで。一般に、コレクションを操作するときは、Kotlinでの書き込みを計画している機能が標準ライブラリによって既に実装されているかどうかを確認し、独自の実装でその機能を使用することを選択してください。

9.プロパティとバッキングプロパティ

自動コンバーターがgetFormattedUserNames()関数を、カスタムgetterを持つformattedUserNamesと呼ばれるプロパティに置き換えていることがわかりました。 内部では、KotlinはListを返すgetFormattedUserNames()メソッドを生成します。
Javaでは、getterおよびsetter関数を介してクラスプロパティを公開します。
Kotlinを使用すると、フィールドで表現されたクラスのプロパティと、機能で表現された機能、クラスで実行可能なアクションをより適切に区別できます。この場合、Repositoryクラスは非常にシンプルで、アクションを実行しないため、フィールドのみがあります。
formattedUserNamesKotlinプロパティの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つのスコープ関数letapplywithrunalsoを作成しました。短く強力なこれらの関数はすべて、レシーバー(this)を持ち、引数(it)を持ち、値を返すことがあります。達成したい内容に応じて、どれを使用するかを決定します。

これを覚えるのに役立つ便利なチートシートを次に示します。
image.png

ここからスコープ機能のチートシートをダウンロードしてください。

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.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
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("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の使用方法については、次のリソースをご覧ください。

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

桁区切り付きで数値を表示する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)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] ProgressBarの使い方

使い方

Widget.ProgressBarを追加して、表示非表示を変えるだけで良い。

・Widget.ProgressBarを追加
スクリーンショット (12).png

表示非表示の切替え

private ProgressBar progressBar;
 // 表示
progressBar.setVisibility(android.widget.ProgressBar.VISIBLE);
// 非表示
progressBar.setVisibility(android.widget.ProgressBar.INVISIBLE);

結果

こういうのが表示されれば成功。
a.png

困ったところ

前回ProgressDialogを扱ったが、https://qiita.com/QiitaD/items/dc6f823e722f5556030f
API非推奨になっていた。現在はProgressBarを使うらしい。見落とさないよう気を付けて頂きたい。

感想

ProgressDialogよりも簡単に使えた。

参考URL

https://akira-watson.com/android/progressbar.html

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

Retrofit2で共通エラー処理。ついでにローディングの判定

APIで共通のエラーレスポンス(400)などがあり、救う手段もないのでとりあえず「通信エラーが発生しました。」みたいなトーストを適当に出すことありますよね。
APIメソッド全てのステータスコードをハンドリングするのは面倒なのでどこかでまとめたいと思いました。
あまりアーキテクチャとかは意識してません

構成

    ├── api
    │   ├── exampleService
    │   │    ├──ExampleApi
    │   │    └──ExampleService
    │   └── HttpManager
    │
    └── ExampleActivity

ExampleActivity → 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クラスなどに持たせればどこからでもアクセスできるのでいい感じです。

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

Expo × React NativeアプリをAndroidで動かした時、TouchableOpacityのonPressが動かない

Expo × React NativeアプリをAndroidで実機確認した時、TouchableOpacityのonPressが動かなかったので共有します。

環境

expo v3.11.7
react-native v36.0.0

解決方法

index.js
import { TouchableOpacity } from 'react-native-gesture-handler';

としていたところを

index.js
import { TouchableOpacity } from 'react-native';

としたら治りました。

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

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

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