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

Unity + DockerでUnityプロジェクトからAARファイルを自動生成

はじめに

UnityのSceneをAndroidのSubView(FrameLayout)として埋め込むまで
https://qiita.com/_nonono/items/253aa15d6027ecc8ad66
このような記事があり、AndroidにUnityのシーンをFrameLayoutに埋め込むことができます(便利

Unityで開発せずビルド成果物(AARファイル)のみ必要な場合
1.指定のバージョンのUnityをインストール
2.Unityを立ち上げAndroidプロジェクトをExport
3.Android Studioでビルド
上の3つの作業を手作業で行うので一苦労だと思います
Unity、Android Studioのバージョンを意識したりしなければならいので良い方法が必要です

「なんとか良い方法を!」と思って Unityの入ったDockerイメージを使ってAARの生成を自動化 してみたので、作業を下に記していきます

TL;DR

いきなり一連の作業をスクリプトにできないので、dockerの中で作業を1つずつして、最後にスクリプトにまとめます

やったこと

  1. docker pull gableroux/unity3d
  2. Dockerコンテナに入りライセンスファイルを用意
  3. ライセンスファイルをコピー
  4. ビルド用クラスを作成
  5. Unityプロジェクトをビルド
  6. build.gradleとAndroidManifest.xmlを編集
  7. GradleでAndroidプロジェクトをビルド
  8. build.shを書いて完全自動化

実際の環境

・MacBook Pro (15-inch, 2016) macOS 10.13.6(17G10007)
・docker desktop ver. 2.1.0.5(40693)

1. Unityの入ったDockerイメージをpullする

まずは、Unityの入っているDockerイメージをインターネットから落としてきます

次のコマンドを実行すると最新版のコンテナが自分のローカルに保存されます

Dockerイメージをpull
docker pull gableroux/unity3d

しかし、このイメージではAndroidプロジェクトを生成/ビルドができないので、UnityバージョンとUnityプロジェクトのビルドターゲットを指定しなければなりません。

この記事では、 Unity 2018.4.5f1 でビルドターゲットをAndroid とするので、Dockerイメージのtagに2018.4.5f1-androidを指定します
※目的のバージョンのイメージが無い場合があるので、 DockerHub のページで確認しておきましょう

目的のDockerイメージをpull
docker pull gableroux/unity3d:2018.4.5f1-android

docker imagesを実行するとpullしてきたイメージが確認できます

DockerImageの確認
cha84rakanal$ docker images
REPOSITORY            TAG                  IMAGE ID            CREATED             SIZE
gableroux/unity3d     2018.4.5f1-android   3cc6afa5c2bd        3 months ago        6.7GB

docker run -it {image name} bash を実行すればイメージからコンテナが作成され、Dokcer内のターミナルに接続できる
なので、接続してUnityAndroid SDKがあるかを確認しておきます

UnityとAndroidSDKの確認
cha84rakanal$ docker run -it --rm gableroux/unity3d:2018.4.5f1-android bash
root@7462f939f4d0:/# echo $ANDROID_HOME
/opt/android-sdk-linux
root@7462f939f4d0:/# ls /opt/
android-sdk-linux  Unity  Unity-2018.4.5f1
root@7462f939f4d0:/# 

2. Dockerコンテナに入ったUnity用のライセンスを生成する(1回のみ・手作業)

普段使うmacOSやWindowsでUnityを一番最初に起動すると次の画面が出てアクティベーションをする必要があります
アクティベーション画面

次の作業は、DockerコンテナにインストールされているUnityのアクティベーションを行います

まず、Unityプロジェクトジェクトのディレクトリに移動してdockerコンテナを立ち上げます
パスワードを平打ちするので気をつけましょう

Dockerコンテナの立ち上げ
cd /path/to/project
docker run -it --rm \
-e "UNITY_USERNAME=username@example.com" \
-e "UNITY_PASSWORD=example_password" \
-e "TEST_PLATFORM=linux" \
-e "WORKDIR=/root/project" \
-v "$(pwd):/root/project" \
gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \
bash

次に、Dockerコンテナ内で以下のコマンドを実行します

アクティベーションをする
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-logFile /dev/stdout \
-batchmode \
-username "$UNITY_USERNAME" -password "$UNITY_PASSWORD"

すると、出力に次のようなXMLが表示されるので、表示されたXMLを unity3d.alf というファイル名で保存します

ターミナルでの出力
LICENSE SYSTEM [2017723 8:6:38] Posting <?xml version="1.0" encoding="UTF-8"?><root><SystemInfo><IsoCode>en</IsoCode><UserName>[...]

もし、XMLではなく401エラーが出た場合は、2段階認証を切りましょう
GoogleでサインインFaceBookでサインイン でUnity IDを作成している場合でも 401エラーがでるので、メールアドレスから作成したUnityIDを使いましょう

401エラー
Can't activate unity: No sufficient permissions while processing request HTTP error code 401

unity3d.alf が用意できたら https://license.unity3d.com/manual にアクセスします。アクセスしたらサイトの指示に従ってunity3d.alfをアップロード、質問に答えていきましょう。すべて終わるとUnity_v2018.x.ulfUnity_v2019.x.ulfがダウンロードできるので保存しておきます。
このUnity_v*.x.ulfファイルは今後必要になるので大切に保管しておきます

プロジェクトチームの誰かがこの作業をやれば、Unityのバージョンを変えない限り、
3以降の作業でUnity_v2018.x.ulfを使いまわすことができます(多分

※アクティベーションの詳しい手順は このページ に書いてありますが、まとめとしてこの記事にも記しています

3. Dockerコンテナに入ったUnityにライセンスを当てる

2の手順でダウンロードしてきたファイルUnity_v2018.x.ulfをUnityプロジェクトのルートディレクトにおきます

Unity_v2018.x.ulfの設置
/path/to/project
├── Assets
├── Library
├── Logs
├── Packages
├── ProjectSettings
├── README.md
└── Unity_v2018.x.ulf

次に、Unityプロジェクトジェクトのディレクトリに移動してdockerコンテナを立ち上げます

Dockerコンテナの立ち上げ
cd /path/to/project
docker run -it --rm \
-v "$(pwd):/root/project" \
gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \
bash

ここから先の作業7.が終わるまでは、dockerコンテナ内のターミナルでexitをしてはいけません

ライセンスファイルをUnityプロジェクトのルートディレクトに設置できたら、dockerコンテナ内のターミナルで次のコマンドを実行します。これでライセンスファイルの設置は完了です

Unityのライセンスファイルを設置
set -e
set -x
mkdir -p /root/.cache/unity3d
mkdir -p /root/.local/share/unity3d/Unity/
set +x
cp ~/project/Unity_v2018.x.ulf /root/.local/share/unity3d/Unity/Unity_lic.ulf

4. ビルド用クラスを作成してAssets/Scripts/Editorに配置

Unityプロジェクトをコマンドラインからビルドできるように次のC#ソースをAssets/Scripts/Editorに設置します

ApplicationBuild.cs
using UnityEngine;
using System;
using System.Linq;

public static class ApplicationBuild {

    private static string[] GetAllScenePaths() {
        return EditorBuildSettings.scenes
            .Where(scene => scene.enabled)
            .Select(scene => scene.path)
            .ToArray();
    }

    public static void AndroidBuild() {

        string[] scenes = GetAllScenePaths();
        BuildPipeline.BuildPlayer(scenes, "./Build/", BuildTarget.Android, BuildOptions.AcceptExternalModificationsToPlayer);

    }

}

公式ドキュメント を確認すると、 ビルドのパラメータを色々設定できます。
今回は、プロジェクトのエクポートパスは./Build 、ビルドターゲットはAndroidなので BuildTarget.Android 、AndroidのビルドはExternalで行うのでオプションに BuildOptions.AcceptExternalModificationsToPlayer を指定しています

BuildPipeline.BuildPlayer
BuildPlayer(EditorBuildSettingsScene[] levels, string locationPathName, BuildTarget target, BuildOptions options)

パラメータ
levels The Scenes to be included in the build. If empty, the currently open Scene will be built. Paths are relative to the project folder (Assets/MyLevels/MyScene.unity).
locationPathName 成果物の保存先のパス
target ビルドする BuildTarget
options ビルドしたプレイヤーを実行するか、などの追加の BuildOptions

5. コマンドラインからUnityプロジェクトをビルド

次のコマンドでUnityプロジェクトをコマンドラインからビルドできます

コマンドラインからUnityプロジェクトをビルド
/opt/Unity/Editor/Unity -batchmode -quit -nographics -logFile ./build.log -projectPath . -executeMethod ApplicationBuild.AndroidBuild

ログは ./build.log に保存されるので、エディタ等で確認するとビルドが進んでるのがわかります
ビルド中は、ターミナルに何かこれといって表示されるものは無いです(無視できるエラーとかは出る

ビルドが完了するとログに次の行が記録されます

build.log
Exiting batchmode successfully now!

※成果物の保存先のパス ./Buildディレクトリがすでにあるとビルドが停止してしまうので消しておきましょう

6. APKではなくAARを作成するようにbuild.gradleとAndroidManifest.xmlを編集

5の作業が終わり、ビルドが成功すると、./BuildディレクトリにAndroidプロジェクトが生成されます

コマンドラインでのビルド後
/path/to/project
├── Assets
├── Build
│   └── {Project Name}
│       ├── build.gradle
│       ├── gradle.properties
│       ├── libs
│       ├── local.properties
│       ├── proguard-unity.txt
│       └── src
├── Library
├── Logs
├── Packages
├── ProjectSettings
├── README.md
└── Unity_v2018.x.ulf

デバイスにインストールする.apkの作成であれば、gradle コマンドでビルドして終了です
今回は、.aarの作成なので、build.gradleAndroidManifest.xmlを編集する必要があります

build.gradle

  • apply plugin: 'com.android.application'apply plugin: 'com.android.library' に変更
  • applicationId 'com.project.unitytest' を削除
  • bundle をコメントアウト(Unity2018.3.x以降??)
build.gradle
--- apply plugin: 'com.android.application'
+++ apply plugin: 'com.android.library'

--- applicationId 'com.project.unitytest'  

--- bundle {
+++ /*bundle {
        language {
            enableSplit = false
        }
        density {
            enableSplit = false
        }
        abi {
            enableSplit = true
        }
--- }
+++ }*/

AndroidManifest.xml

  • 該当のintent-filterタグとその子をすべてコメントアウト
AndroidManifest.xml
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.MAIN" />-->
<!--<category android:name="android.intent.category.LAUNCHER" />-->
<!--<category android:name="android.intent.category.LEANBACK_LAUNCHER" />-->
<!--</intent-filter>-->

UnityプロジェクトからAARファイルの生成を行うには、この作業も自動化しておく必要があるので、
UnityのPostProcessの機構を使って自動化しておきます

[Unity] PostProcessでビルド後に処理を差し込む
https://qiita.com/edo_m18/items/346439f7678218e85e69

build.gradleの文字コードは UTF-8 で書き出すこと、 C#のでのUTF8EncodingUTF-8 with BOMであるので注意が必要。 UTF-8 with BOMbuild.gradlegradleに渡すとエラーになるので注意
※UnityのPostProcessBuildにはこの記事ではふれません
build.gradlebundleのコメントアウトだが このページ によれば次に示す置換でも問題ない

build.gradle
--- bundle {
+++ splits {
        language {
---         enableSplit = false
+++         enable false
        }
        density {
---         enableSplit = false
+++         enable false
        }
        abi {
---         enableSplit = true
+++         enable true
        }
    }
bundle部分を処理するC#の例
build_text = build_text.Replace("bundle {", "splits {");
build_text = build_text.Replace("enableSplit = false", "enable false");
build_text = build_text.Replace("enableSplit = true", "enable true");

7. コマンドラインでAndroidプロジェクトをビルド

残る作業は./Buildディレクトリに生成されたAndroidプロジェクトをビルドしてAARファイルを生成するだけです
生成されたAndroidプロジェクトのディレクトリ(build.gradleがあるディレクトリ)に移動して次のコマンドを実行するだけです

AARファイルを生成
gradle bundleDebugAar
AARファイルを生成
gradle bundleReleaseAar

デバッグとリリースがあるので必要に応じて使い分けましょう

ビルドが成功するとプロジェクトのディレクトリにbuildディレクトリが生成されて、
その中に {PROJECT_NAME}-debug.aar {PROJECT_NAME}-release.aar が生成されます

ビルド後
/path/to/project
├── Assets
├── Build
│   └── {Project Name}
│       ├── build
│       │   └── outputs
│       │       └── aar
│       │           ├── {PROJECT_NAME}-debug.aar
│       │           └── {PROJECT_NAME}-release.aar
│       ├── build.gradle
│       ├── gradle.properties
│       ├── libs
│       ├── local.properties
│       ├── proguard-unity.txt
│       └── src
├── Library
├── Logs
├── Packages
├── ProjectSettings
├── README.md
└── Unity_v2018.x.ulf

8. 作業をシェルスクリプトにまとめる

4から7の作業をまとめて、コマンド一行でAARファイルの生成できるようにします
ライセンスファイルはUnityプロジェクトのルートディレクトリに設置して、6の作業はUnityのPostProcessBuildで自動化しておきましょう

そうして作業をまとめたスクリプトが次になります

build.sh
#!/usr/bin/env bash

set -e
set -x
mkdir -p /root/.cache/unity3d
mkdir -p /root/.local/share/unity3d/Unity/
set +x

cp ~/project/Unity_v2018.x.ulf /root/.local/share/unity3d/Unity/Unity_lic.ulf

cd ~/project/ && rm -r ./Build && /opt/Unity/Editor/Unity -batchmode -quit -nographics -logFile ./build.log -projectPath . -executeMethod ApplicationBuild.AndroidBuild
cd ~/project/Build/{Project Name} && gradle bundleDebugAar

Unityプロジェクトのルートディレクトリに移動して、dockerコンテナ内でbuild.shを実行します

AARの自動ビルド
cd /path/to/project
chmod 777 build.sh
docker run -it --rm \
-v "$(pwd):/root/project" \
gableroux/unity3d:gableroux/unity3d:2018.4.5f1-android \
/bin/bash -c "/root/project/build.sh"

さいごに

これでコマンドを実行してビルドされるのを待つだけになりました!
これをBitbucket PipelineCircleCIGitHub Actions とかのCIツールに載せていけるといいですね
まとめている間に、既にmacOSやWindowsにUnityがインストールされていて、かつ、Android SDKがインストールされているなら、Docker上じゃなくていいかなと思いました

余談

実際の出力
cha84rakanal$ time docker run -it --rm -e "WORKDIR=/root/project" -v "$(pwd):/root/project" gableroux/unity3d:2018.4.5f1-android /bin/bash -c "/root/project/build.sh"
+ mkdir -p /root/.cache/unity3d
+ mkdir -p /root/.local/share/unity3d/Unity/
+ set +x
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default
/home/builduser/buildslave/unity/build/Editor/Platform/Linux/UsbDevices.cpp:UsbDevicesQuery

Welcome to Gradle 5.1.1!

Here are the highlights of this release:
 - Control which dependencies can be retrieved from which repositories
 - Production-ready configuration avoidance APIs

For more details see https://docs.gradle.org/5.1.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :help

Welcome to Gradle 5.1.1.

To run a build, run gradle <task> ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

To see more detail about a task, run gradle help --task <task>

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 5s
1 actionable task: 1 executed
debugger-agent: Unable to listen on 28
Starting a Gradle Daemon (subsequent builds will be faster)

Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/5.5.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 2m 7s
21 actionable tasks: 21 executed

real    33m9.065s
user    0m0.162s
sys     0m0.178s
cha84rakanal$ 

timeコマンドで実行時間を図ったら30分もビルドしてるので、dockerコンテナ内よりホスト側でビルドした方がはやいやんけw

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

Flutter desktop embedding を Ubuntu にインストール&試してみる

Flutterで書いたアプリケーションをデスクトップ環境でも動かせるようにする、Flutter desktop embeddingをUbuntu 18.04環境で試してみたので、その手順をまとめておきます。

環境構築の手順

Flutter SDKインストール

リポジトリからcloneした後は、適当なパスに配置して、パスを通しておきます。

$ git clone https://github.com/flutter/flutter
$ sudo mv flutter /usr/local/
$ export PATH=$PATH:/usr/local/flutter/bin

Android Studioインストール

https://developer.android.com/studioからLinux版をダウンロードして適当な場所に置いて、インストールスクリプトを叩きます。

Android Studioのインストールで基本的には次へを押していく感じです。

$ wget https://developer.android.com/studio](https://developer.android.com/studio
$ sudo tar -xzvf android-studio-ide-191.5977832-linux.tar.gz -C /usr/local/
$ /usr/local/android-studio/bin/studio.sh でAndroid Studioをセットアップ

その他のインストール

サンプル実行(flutter run)時に必要になるので、clang++をインストールしておきます。

$ sudo apt install clang

Flutterセットアップ

ターゲット環境をHost PC (Ubuntu) にする設定を行います。参考

$ flutter config --enable-linux-desktop

ここまでの状況の確認

以下のコマンドを使うと、flutterを動かすためのインストール状況を診断してくれます。今回はサンプルを動かせればいいので、以下の状態でも大丈夫です。

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel master, v1.12.3-pre.49, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Linux toolchain - develop for Linux desktop
[!] Android Studio (version 3.5)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.40.1)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[!] Proxy Configuration
    ! NO_PROXY does not contain 127.0.0.1
[✓] Connected device (1 available)

! Doctor found issues in 3 categories.

サンプルを実行してみる

flutter-desktop-embedding取得

$ git clone https://github.com/google/flutter-desktop-embedding.git

サンプルを実行してみる

$ cd flutter-desktop-embedding/example
$ flutter run

もしくは、事前にビルドしておくなら以下。

$ flutter build linux
$ flutter run

ソースコードは./lib/main.dartみたいです。

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

Android Builderの使い方

本内容は以下のサイトを参照して作ったです。
https://blog.csdn.net/androidxiaogang/article/details/80586631 

// static 内部クラスの使い方
外部クラスの属性は多いし、可変し、絶対必須ではない。
そして、使用者によって非必須の属性を設定するのは使用者次第なので、
普通のやり方で、各属性のset関数でやります。
或いは、いっぱいのコンストラクタ関数を用意する。

Builderのコンストラクタ関数は可変で、使い方は便利である。
個人的な感じはBuilderパータンの方法は普通の使い型より少しだけを簡単になる。

以下のクラスの使い方法は
NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();

サンプルコード:

public class TestStaticClass {

private final int servingSize; // サイズ
private final int servings;//食べ物
private final int calories;//カロリー
private final int fat;//脂肪
private final int sodium;//Na
private final int carbohydrate;//炭水化物

@Override
public String toString() {
    return "NutritionFacts{" +
            "servingSize=" + servingSize +
            ", servings=" + servings +
            ", calories=" + calories +
            ", fat=" + fat +
            ", sodium=" + sodium +
            ", carbohydrate=" + carbohydrate +
            '}';
}

public static class Builder {
    //必須属性
    private int servingSize;
    private int servings;
    //可選の属性
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    //必須の属性を追加
    public Builder(int servingSize, int servings) {
        this.servingSize = servingSize;
        this.servings = servings;
    }

    public Builder calories(int calories) {
        this.calories = calories;
        return this;
    }

    public Builder fat(int fat) {
        this.fat = fat;
        return this;
    }

    public Builder carbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
        return this;
    }

    public Builder sodium(int sodium) {
        this.sodium = sodium;
        return this;
    }

    //外部クラスにオブジェクトを作成
    public TestStaticClass build() {
        return new TestStaticClass(this);
    }
}

private TestStaticClass(Builder builder) {
    servingSize = builder.servingSize;
    servings = builder.servings;
    calories = builder.calories;
    fat = builder.fat;
    sodium = builder.sodium;
    carbohydrate = builder.carbohydrate;
}

}

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

[ Android ] バージョンを指定する方法

androidでバージョンを指定するにはbuild.gradleファイルを用いる。

ファイルの場所

まずプロジェクトを表示する。
スクリーンショット (164).png

そのなかにbuild.gradleファイルがあるので、これを開く。
スクリーンショット (165).png

そこに以下のような記述がある。ここを変更する。
スクリーンショット (166).png

compileSdkVersion

APK作成時にコンパイルで使用するSDKのバージョンを指定。

buildToolsVersion

ビルド時に使うツールのバージョンを指定。

targetSdkVersion

アプリを動作させるバージョンを指定。

minSdkVersion

端末がインストールできる最低のバージョンを指定。

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

【Flutter】VSCode上でIntegration Testを動かす

FlutterのIntegration Testは、公式のcookbookに載っている方法をそのまま利用すると、いくつか不便な点があります。

  • 毎回コマンドラインから実行する必要がある
  • 実行するたびにアプリのビルドが走って時間がかかる
  • ブレイクポイントを打ってデバッグすることができない

ということでなんとかしましょう。

対象IDEはVSCodeです。

参考元

こちらのMedium記事を大いに参考にします。
https://medium.com/flutter-community/hot-reload-for-flutter-integration-tests-e0478b63bd54

内容をザッッッッックリと要約すると

  • 通常flutter driveは次の2つのプロセスを動かして相互通信させる
    • アプリ自体(シミュレーター上)
    • テストコード(単なるDartプログラム)
  • ならば、それぞれを自分でIDE上で動かして通信させれば良い
  • 結果、Hot Reload/Hot Restart、ブレイクポイントなど自由自在

まあ、ザックリこういうことです。

リンク先の記事内ではAndroid Studioでの例が載っているのですが、

これを、VSCodeでやりたい!!というのが今回の目的です。

なお、こちらのQiita記事も参考にさせていただいたのでリンクを貼っておきます。

https://qiita.com/sensuikan1973/items/c8b56dfaf780e61af567

ではやり方を見ていきましょう。

使うアプリ

アプリ自体は何でもいいです。今回はこうしました。

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

void main() =>
    runApp(MaterialApp(home: Scaffold(appBar: AppBar(), body: MyApp())));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final Hoge _hoge = Hoge();
  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Text(_hoge.text, key: const Key('text')),
      RaisedButton(
        key: const Key('button'),
        child: const Text('button'),
        onPressed: () {
          setState(() {
            _hoge.change();
          });
        },
      )
    ]);
  }
}

class Hoge {
  String text = 'hogehoge';
  void change() {
    if (text == 'hogehoge') {
      text = 'fugafuga';
    } else {
      text = 'hogehoge';
    }
  }
}

ボタンを押すたびに、hogehogeとfugafugaが切り替わります。それだけ。

テストコードから中身にアクセスするため、Keyが設定してあります。

テスト用にアプリを起動するためのコードは次のような感じ。

test_driver/app.dart
import 'package:flutter_driver/driver_extension.dart';
import 'package:stepbystep/main.dart' as app;

void main() {
  // app_test.dart の方とやりとりしたい場合はこの引数にhandlerを追加
  enableFlutterDriverExtension();

  // runAppに好きなWidgetを渡しても良い
  app.main();
}

アプリ自体(シミュレーター上)を動かす

launch.jsonに、configurationを追加して次のようにします。

追加したconfigurationの名前と、ポート番号は適当です。

launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "App of Integration Test",
      "type": "dart",
      "request": "launch",
      "program": "test_driver/app.dart",
      "args": ["--observatory-port", "8888", "--disable-service-auth-codes"]
    },
    {
      "name": "Flutter",
      "request": "launch",
      "type": "dart"
    }
  ]
}

この状態で、VSCode上から今追加したconfigurationを指定してデバッグを開始すると、test_driver/app.dartに記述したアプリがシミュレーター上で走ります。

いつものようにHotReloadやHotRestartも可能です。ブレイクポイントも機能します。

そして重要な点は、ポート番号8888でこのアプリにアクセス可能になるということです。

試しにブラウザを開いて、アドレスバーに

http://localhost:8888/

と打ち込んでみましょう。

こんな感じの画面が出れば、確かにこのポート番号でFlutterが動いていることがわかります。

Screen Shot 2019-11-24 at 19.49.30.png

細かい注意(DEBUG CONSOLEの出力)

configurationを指定してデバッグ開始した場合、DEBUG CONSOLEの出力がconfigurationごとに別々になるので注意してください。DEBUG CONSOLE内の右の方にプルダウンリストがあるのでそこから選択すればOK。

テストコードの実行

Integration Testの公式cookbookでは、テストコードは/test_driver/ディレクトリに入れることになっていますが、

VSCode上でテストコードをテストとして実行するためには、

_test.dartで終わるファイル名がついたコードを/test/ディレクトリ内に配置する必要があります。

(参考:v3.6 - Dart Code - Dart & Flutter support for Visual Studio Code)

test/integration_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  FlutterDriver driver;

  setUpAll(() async {
    driver =
        await FlutterDriver.connect(dartVmServiceUrl: 'http://localhost:8888/')
            .timeout(Duration(seconds: 10));
  });

  tearDownAll(() async {
    if (driver != null) {
      await driver.close();
    }
  });

  test('the button changes the text from hogehoge to fugafuga', () async {
    expect(await driver.getText(find.byValueKey('text')), equals('hogehoge'));
    driver.tap(find.byValueKey('button'));
    expect(await driver.getText(find.byValueKey('text')), equals('fugafuga'));
  });
}

FlutterDriver.connect()dartVmServiceUrl引数を与えると、そのURLを使ってアプリに接続しようとします。

なので、先程ブラウザで開いたのと同じhttp://localhost:8888/を指定しましょう。

(timeoutについては「問題点」の所で後述します)

あとは、VSCode上でこのファイルを開いた状態で、configurationにいつものFlutterを指定し、デバッグを実行すればOKです。

Integration Testが実行されます。

テストコード内にブレイクポイントを打つと、きちんと機能します。

アプリを起動したまま、テストコードだけ繰り返し実行することもできます。

プロセスを終了させる

これでテストはできましたが、実はテストを終了しても裏でプロセスが残っています。

メモリ・CPUの観点からもよくないし、port 8888も占有されたままです。

そのまま同じアプリの開発を続けるなら恐らく問題ないのですが、

違うアプリの開発に移ってから同様にIntegration Testをすると次のエラーが出ます。

flutter: Could not start Observatory HTTP server:
SocketException: Failed to create server socket (OS Error: Address already in use, errno = 48), address = 127.0.0.1, port = 8888

なので、このプロセスを終了させましょう。

次のコマンドを実行します。

lsof -i :8888

すると、port 8888を使用しているプロセスの一覧が出るので、PIDを指定してkillします。

kill (PIDを指定)

テスト終了時に自動でプロセスも終了したらいいんですけどね。そのような方法をご存知の方は教えていただけると嬉しいです!!!

問題点

実はこの方法ですが、けっこうでかい問題点が含まれております。

何か解決案をお持ちの方は教えていただけると嬉しいです!

アプリ側の状況によってテスト結果が変わる

「アプリを1から起動し、テストを実行して、アプリを閉じる」という一連の流れを手動で断ち切ってしまっていますので、

アプリを操作したり、テストコードを連続で実行したりすると、結果に影響します。

例えばこの記事の例だと、Integration Testを一度実行するとtextがfugafugaに切り替わっている(初期値はhogehoge)ので、そのままもう一度テストを実行すると2回目は失敗します。

テストの独立性を保証する工夫が必要です。

とりあえず毎回アプリをHot Restartするのが一番ラクな気がします。

Dart: Run All TestsでIntegration Testも実行されてしまう

Unit TestとWidget Testだけ全て実行したい場面でも、/test/に配置したIntegration Testも一緒に実行されてしまいます。

その際にテスト用アプリが実行されていなければ、当然エラーになります。

上記コードではとりあえずFlutterDriver.connectにタイムアウトを設定してありますので、アプリが実行されていない場合はここでタイムアウトしてIntegration Testが終了します。

でもそうすると、「Integration Test 以外のテストが全て通れば良しとする」という風に結果を自分で判断しなければいけません。

自動テストや自動デプロイを組んでいると影響が出そうです。

ただ、自動テストの場合はVSCode上で実行するわけではないので、自動で実行するテストをうまく指定できれば問題ないかもしれません。

Unit TestとWidget Testを/test/unit_and_widget_test/のようなディレクトリにまとめて、launch.jsonでこのディレクトリを指定してまとめて実行することも考えましたが、

この方法だと、一つのファイルを実行したらシェルが終了してしまうらしく、次のファイルを実行する際にエラーになりました。残念。

一応解決策

FlutterDriver.connectが失敗した場合にフラグ立てをして、テストケースの中身をif文で回避する手があります。

test/integration_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  FlutterDriver driver;
  bool doTest = true;

  setUpAll(() async {
    driver =
        await FlutterDriver.connect(dartVmServiceUrl: 'http://localhost:8888/')
            .timeout(Duration(seconds: 3), onTimeout: () {
      print('Flutter driver connection failed!!!!!');
      doTest = false;
      return null;
    });
  });

  tearDownAll(() async {
    if (driver != null) {
      await driver.close();
    }
  });

  test('the button changes the text from hogehoge to fugafuga', () async {
    if (doTest) {
      expect(await driver.getText(find.byValueKey('text')), equals('hogehoge'));
      driver.tap(find.byValueKey('button'));
      expect(await driver.getText(find.byValueKey('text')), equals('fugafuga'));
    }
  });
}

無理やり回避した感がすごいですが、一応、Run All Testsの際も、Integration Test以外がすべて通ればOKを出してくれます。

(setUpAllはテストケースの実行が決定してから呼ばれるため、test関数のskip引数はうまく使えない)

おわり

この記事は以上です!

Integration Testでもアプリコードやテストコードにブレイクポイントを打ってデバッグできるのでとっても便利です!

これでストア申請に必要なスクショも撮れますね。

でも問題点には注意が必要です。

最後に。記事に載せようと思ってUnit TestやWidget Testも書きましたが、載せるタイミングがありませんでした。何が誰の参考になるかもわからないので載せておきます。

test/unit_test.dart
import 'package:test/test.dart';
import 'package:stepbystep/main.dart';

void main() {
  test('change method changes text', () {
    final Hoge hoge = Hoge();
    expect(hoge.text, equals('hogehoge'));
    hoge.change();
    expect(hoge.text, equals('fugafuga'));
    hoge.change();
    expect(hoge.text, equals('hogehoge'));
  });
}

test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stepbystep/main.dart';

void main() {
  testWidgets('button changes text',
      (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(home: MyApp()));
    expect(find.text('hogehoge'), findsOneWidget);
    await tester.tap(find.byKey(const Key('button')));
    await tester.pumpAndSettle();
    expect(find.text('fugafuga'), findsOneWidget);
    await tester.tap(find.byKey(const Key('button')));
    await tester.pumpAndSettle();
    expect(find.text('hogehoge'), findsOneWidget);
  });
}

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

adb - Android debug tool

Enable Device Debug

  • Enable developer mode on your Android device
    1. Settings ⇒ System ⇒ About Phone ⇒ Tap "Build number" over 7 times and quickly
    2. Then you will see "You are already developer"
  • Enable debugging
    1. Go to "Developer Options" ⇒ Enable "ADB debugging"
    2. Connect Android phone to your Windows PC, then you will see a pop-up ( some phone will not show pop-up, please check notification center)
    3. Choose "Transfer files"

adb Setup

Windows

    cd C:\{adb folder path}

Mac

Check installation success

  • Type
    adb
  • If you can see below message, means adb is able to use
    Android Debug Bridge version 1.0.40
    Version 4986621
    Installed as C:\{adb folder path}\adb\adb.exe

    global options:
     -a         listen on all network interfaces, not just localhost
     -d         use USB device (error if multiple devices connected)
     -e         use TCP/IP device (error if multiple TCP/IP devices available)
     -s SERIAL  use device with given serial (overrides $ANDROID_SERIAL)
     -t ID      use device with given transport id
     -H         name of adb server host [default=localhost]
     -P         port of adb server [default=5037]
     -L SOCKET  listen on given socket for adb server [default=tcp:localhost:5037]

adb command

  • Connect your Android phone to PC/MAC, then type
    adb devices

If you see below message, means your Android phone already connected to PC, and able to use adb command

    List of devices attached
    35PDU18712000000        device
  • Send text to device
    adb shell input text "textttt"
    adb shell input keyevent TAB
    adb shell input keyevent 61
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

トラブルシューティング of AVD @ Ubuntu18.04

image.png

PCのBIOSで「Virtualization Technology」 をEnableに変更する

image.png

sudo apt install qemu-kvm
sudo adduser $USER kvm


としてリブート

起動に時間がかかる

コマンドラインで一旦起動したら次回以降OKとなりました。

https://developer.android.com/studio/run/emulator-commandline

コマンドラインで起動

~/Android/Sdk/emulator$ ./emulator -avd Nexus_5X_API_28

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

[Android]TextView の文字の大きさを自動的に調整する(Autosizing TextViews)

はじめに

Android の TextView では View サイズに応じて、
文字の大きさ(TextSize)を自動的に調整する機能が実装されています。
その機能について調べたので、簡単に使い方をまとめたいと思います。

View サイズに応じて TextSize を自動的に調整する

次のように TextView の autoSizeTextType を uniform に設定すると、
TextView の View サイズに応じて TextSize が自動的に調整されるようになります。

activity_main.xml
        <TextView
            android:id="@+id/size_type_uniform_view"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:text="Uniform"
            android:background="#aaffaa"
            app:autoSizeTextType="uniform"/>

次のような感じで View サイズの変化に応じて TextSize を自動的に調整してくれます。

ezgif.com-rotate.gif

TextSize を大きくするステップ数の調整をする

次のように autoSizeStepGranularity を設定すると、
TextSize を大きくするステップ数の調整ができます。

activity_main.xml
        <TextView
            android:id="@+id/granularity_view"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:text="Granularity"
            android:background="#aaffaa"
            app:autoSizeTextType="uniform"
            app:autoSizeStepGranularity="10dp"/>

次のような感じで TextSize を徐々に大きくするのではなく段階的に大きくしてくれます。

ezgif.com-rotate (1).gif

TextSize の最小値と最大値を調整する

次のように autoSizeMinTextSize と autoSizeMaxTextSize を設定すると、
View サイズに応じて調整される TextSize の最小値と最大値を調整できます。

activity_main.xml
        <TextView
            android:id="@+id/min_max_view"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:text="MinMax"
            android:background="#ffaaaa"
            app:autoSizeTextType="uniform"
            app:autoSizeMinTextSize="25dp"
            app:autoSizeMaxTextSize="75dp"/>

次のような感じで、ある View サイズよりも小さくなったら TextSize が小さくならず
ある View サイズよりも大きくなったら TextSize を大きくしないようにできます。

5dd9612db1b05307488279.gif

プリセットで ステップ数、最小値、最大値を定義する

autoSizeStepGranularity や autoSizeMinTextSize 、autoSizeMaxTextSize を
設定すればTextSize をどのように大きさを調整するか大まかに設定できました。

もっと詳細に調整したい場合は autoSizePresetSizes を利用します。
次のような配列(プリセット)を渡すと、TextSize をそのステップ数、最小値、最大値で調整してくれます。

arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="autosize_text_sizes">
        <item>10dp</item>
        <item>50dp</item>
        <item>100dp</item>
    </array>
</resources>
activity_main.xml
        <TextView
            android:id="@+id/size_preset_sizes_view"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:text="PresetSizes"
            android:background="#aaaaff"
            app:autoSizeTextType="uniform"
            app:autoSizePresetSizes="@array/autosize_text_sizes"/>

次のような感じでプリセットのステップ数、最小値、最大値となるよう TextSize を調整してくれます。

5dd966f85c678518635109.gif

おわりに

TextView の文字の大きさを自動的に調整したいときは、
次の attributes にて どのように自動的に調整するか設定できる。

attributes 説明
autoSizeTextType uniform であれば自動的に調整する。
none であれば自動的に調整しない。
autoSizeStepGranularity 自動的に調整する際の TextSize を大きくするステップ数を設定する。
autoSizeMinTextSize 自動的に調整する際の TextSize の最小値を設定する。
autoSizeMaxTextSize 自動的に調整する際の TextSize の最大値を設定する。
app:autoSizePresetSizes 自動的に調整する際の プリセット を設定する。
プリセットに定義した内容で ステップ数、最小値、最大値が決まる

参考文献

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

【Unity】これ以上セーフエリア対応で消耗しないためのアセットを作った【AutoScreen】

AutoScreen(GitHub)

AutoScreen_01.gif
AutoScreen_02.gif

はじめに

Unityにおけるセーフエリアの対応に関してはScreen.safeAreaというAPIがあるのですが、ビルドして実機で呼ばないと端末ごとの値が取れない=エディタ上ではレイアウトの確認ができないという辛さがあります。

最近だとUnite Tokyo 2019でも紹介されたDevice Simulatorが記憶に新しいところですが、このパッケージはUnity2019.3からでないと使用できず、まだプレビュー版となっています。

個人的な感覚ではUnityはたとえLTS版であってもある程度時間が経つまでは本番で使用するのが怖いので、自分が実際にDevice Simulatorを使えるようになるのは2020年の後半になる気がしています。

そんなに待ってられないよ!ということでUniSafeAreaAdjusterをありがたく使わせてもらってたのですが

  • 解像度に合わせて端末を選択するのが面倒
    • 特に複数のGameObjectSafeAreaAdjusterコンポーネントをつけてるとき
  • デバイスのフレーム表示機能がほしい
  • 手軽に対応端末を追加・拡張できるようにしたい

という気持ちがピークに達して自分でアセットを作ってしまったのでご紹介します。

便宜上「アセット」と書いてますがアセットストアに公開するのはけっこう面倒なのでGitHubでだけ公開しています。

↓↓↓↓↓↓↓
AutoScreen(GitHub)

READMEがまだないのですが、そのうち本記事を元に英語で書くと思います。

機能

  • モバイル実機上でセーフエリアに応じてRectTransformのアンカーを自動調整するコンポーネント
    • SafeArea:セーフエリア用(適用したい方向を上下左右自由に組み合わせ可)
    • UnsafeArea:非セーフエリア用(上下左右どれか選択)
    • RuntimeSafeAreaUpdater:画面の回転を自動検知してSafeArea,UnsafeAreaを更新
  • エディタ上でセーフエリアをリアルタイムプレビュー
    • 既存のGameウィンドウでセーフエリアあり端末の解像度を選択するだけ(追加作業なし)
    • 再生/非再生の状態に関係なく即時反映
  • on/off可能なオプション
    • デバイスのフレームを表示
    • セーフエリアの境界線表示

対応状況

  • Unity2018.3以降対応(Device Simulatorは未対応)
  • iOS:実機・エディタともに対応
    • iPhone X/XS/11 Pro
    • iPhone XS Max/11 Pro Max
    • iPhone XR
    • iPad Pro (第3世代, 11インチ)
    • iPad Pro (第3世代, 12.9インチ)
  • Android:実機のみ対応

Android端末のエディタプレビューは以下の理由でオミットしています。

  • 個人的にAndroid端末にビルドする必要性がない
  • フレーム画像と解像度・セーフエリアデータの収集に手間がかかる
  • 対応が必要な端末数が多そう

端末のマスターデータの追加自体は簡単なので、必要に応じて後述の手順を参考に自分で足してください。

インストール方法

リポジトリ内に*.unitypackageファイルがあるのでこちらを使用してください。

使い方

とりあえず動作を確認してみたい場合はDemoシーンを用意してあるのでいじってみたりビルドしてみてください。

セーフエリアの自動調整機能

Canvas直下のGameObjectSafeArea/UnsafeAreaコンポーネントをAddComponentするとRectTransformAnchorが自動で調整されます。直下じゃなくてもCanvasに至るまでの親GameObjectRectTransformがすべて縦横に完全にストレッチするようにしてあれば正常に動作します。

スクリーンショット 2019-11-24 11.27.57.png
GameObjectが存在する場合のRectTransformの設定

デバイスのフレーム表示/セーフエリアの範囲表示

セーフエリアありの端末解像度を選択するとGameウィンドウの左上に歯車アイコンが表示されるので、そこから表示・非表示を切り替え可能です。

こだわった点

基本的には以下の3点を追求しました。

  • uGUIのオートレイアウトのような簡単で自然な使い心地
  • 使ってて細かい挙動を含めイライラしない
  • シンプルかつ高い拡張性

リアルタイムプレビュー

  • エディタ上で
  • 既存のGameウィンドウの解像度選択を変更するだけで
  • リアルタイムに
  • 再生/非再生の状態を問わず
  • セーフエリアあり端末での見た目が自動で調整

されるので、ビルドしなくてもセーフエリアありの場合のレイアウトを手軽に確認できます。

設定変更がGameウィンドウから可能

通常アセットやエディタの拡張の設定は[MenuItem]を使用してグローバルメニュー(+ショートカットキー)から行えるようにするのですが、Gameウィンドウ上に設定のUIを配置することでon/off切り替えの煩雑さを軽減しました。

ちなみにUIの配置にはUIElementsを使用しています。

高い拡張性

端末解像度とセーフエリアの情報はScriptableObjectを継承したアセットとして保持しているので、追加・拡張が簡単にできます。

SafeArea,UnsafeAreaコンポーネントで満たせない複雑な要件の場合も、SafeAreaBaseクラスを継承することでエディタ上でのリアルタイムプレビューの機能が簡単に実装できます。

Gitフレンドリー

セーフエリアの対応はRectTransformのアンカーを自動調整することで実現していますが、シーンやプレハブの保存前に必ずアンカーをリセットしています。

これにより「Gameウィンドウで異なる解像度を選択して保存 → Gitでdiffが出る」ということが起きません。

実装の詳細

Gameウィンドウの情報を利用するにあたり、Gameウィンドウの実態であるGameViewクラスが公開されていないため、リフレクションを用いてそのデータにアクセスしています。

実際にリフレクションを用いているのはGameViewProxyクラスのみで、値の変更についてはGameViewEventクラスが監視・イベント化しています。

実機のフレーム表示は前もって用意したフレーム画像を描画していて、セーフエリアの大きさは事前にシミュレータビルドで収集した値をScriptableObjectを継承したGameViewScreenアセットに保存・使用しています。

デバイスのフレーム表示とセーフエリアの境界線表示はそれぞれDeviceFrameDrawerコンポーネントとSafeAreaDrawerコンポーネントが行っていて、それらのコンポーネントがアタッチされたAutoScreenManagerプレハブが自動的にHierarchyに配置されるようになっています。このプレハブインスタンスはシーンやビルドには含まれず、Hierarchyにも表示されません。

対応端末追加の手順

エディタ上でのプレビューはマスターデータを追加するだけで簡単に対応機種を増やすことが可能です。

  1. Gameウィンドウから手作業で解像度を追加します。自動化したい場合はGameViewSizeHelperを使うと良いです。
  2. Projectウィンドウで右クリックし、Create -> ScriptableObjects -> GameViewScreenからマスターデータを保持するためのアセットを作成します。
  3. 2.で作成したアセットの各値をInspectorウィンドウから設定します。
    • 解像度やセーフエリアの値はDemoシーンをシミュレータや実機にビルドして確認すると楽です。
    • Base Textは1.で追加した解像度のLabelと同じ文字列にしてください。
    • 必要であればデバイスのフレーム画像を追加し、アセットのFrameにセットしてください。
  4. 解像度を適当に変更すると反映されます。Unityを再起動する必要はありません。

Tips

  • 自動で追加されるRuntimeSafeAreaUpdaterコンポーネントは画面の回転を許可していないアプリの場合は不要
  • 11インチのiPadはGameウィンドウから解像度を登録するとプレビュー可能
    • デフォルトだとUnityに解像度が登録されてない
    • ラベル名は「iPadPro 2388x1668 Landscape」「iPadPro 2388x1668 Portrait」で登録する
  • 12インチのiPadの解像度を選択するとセーフエリアあり(第3世代)としてプレビュー表示される
    • 第1〜2世代のセーフエリアなしレイアウトを確認したい場合は適当なラベル名で別途解像度を追加すればOK
  • セーフエリア境界線の太さ・色はAutoScreenManagerプレハブから調整可能
  • SafeAreaDrawerコンポーネントは単体で実機でも動作可能

ライセンス

MIT

参考

最後に

やりたいことや細かい挙動の調整を全部やろうとしたら結果的に

  • 新しいプレハブAPIの使い方
  • UIElements
  • [ExecuteAlways]
  • HideFlags

等々Unityのよく知らなかった様々な機能について詳しく知る良い機会になりました。個別の知見についてはアドベントカレンダーのときにでもまとめたいと思います。

コードにコメント書かないマンなのですがコード自体はシンプルで読みやすいと思うので、わからないことがあったらとりあえずコードを読んでみてください。

実は実践未投入なので、何か不具合があればPRかTwitterへ → @su10_dev

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

Scoped Model を利用した場合の画面遷移でうまくいったコード例(Flutter)

最近Flutterをいじり始めました。Scoped Modelを利用するとスッキリ書けるので、そのコードを書いたときの画面遷移時に具体的にどうコードを書くとうまくいったかをレポートします。

[Scoped Model 初めて聞いた方へ]Scoped Model とは

公式のパッケージサイトは下記です。

https://pub.dev/packages/scoped_model

基礎原理の解説について下記記事が参考になりました。

https://qiita.com/hayassh/items/690fa0d6528e056617b5

自分の言葉で書くと、数値や文字列の処理部分とデザインテンプレート部分をModel定義をすることで綺麗に分離ができます。あと、このパッケージは、GoogleのFlutter開発チームが提供してくれているVeggie Seasonsというアプリのコードにも採用されていたので、自分もやってみようと思いました。Veggie Seasonsのコードは以下です。

https://github.com/flutter/samples/tree/master/veggieseasons

ページ遷移時の試行錯誤時のエラー解決で得た知見

今回はこちらを共有したいのです。Scoped Modelを使わない場合と比較できるように書いてみました。

#main.dart
//1:route定義をMaterialAppのところで行って、

void main() {

  runApp(MaterialApp(
    title: 'Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => FirstScreen(),
      '/second': (context) => SecondScreen(),
    },
  ));
}

//2:↓最初のページに当たるFirstScreen部分のStatelessWidget記述です。

class FirstScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('Demo'),
      ),
      body: Center(

      // 中略
      child: RaisedButton(
          padding: EdgeInsets.all(12.0),
          shape: StadiumBorder(),
          child: Text(
            "次のページへ",
            style: TextStyle(fontSize: 20.0, color: Colors.white),
          ),
          color: Colors.green,
          onPressed: () {

            Navigator.pushNamed(context, "/second");
            //3:↑で次のページ"SecondScreen()"へ移動クラス生成です。
          },
        ),

//次の画面(SecondScreen)
class SecondScreen extends StatelessWidget {

   //以下略SecondScreenの表示内容などが書かれています。

}

↑これをScoped Model 活用して記述する際、
下記のように書くとうまくいきました。  

#main.dart
import 'model.dart'; 
/*
↑model(今回はDemoModelという名称です)を別ファイルで定義しました。
このmodelそのものの部分の解説は、
ページ遷移の解説に戻るまでにかなり時間がかかるので割愛します。
※本記事前述の解説などの参考になる記事が複数存在しますので、ご覧ください。
*/

import 'package:scoped_model/scoped_model.dart';
// scoped_modelのパッケージをインポートします。

DemoModel demoModel = DemoModel();
//modelを生成しておきます。

void main() {

  runApp(MaterialApp(
    title: 'Demo',
    home: ScopedModel<DemoModel>(
        model: demoModel,
        child: new FirstScreen()
    )
  ));
}

/*
route指示部分がごっそり無くなっています。書いてみるとエラーになりました。
遷移時に各ボタンなどの実行時のコードに
Scoped Modelの子孫のクラスとなる次のページのクラス生成を指示することになります。
*/

//最初の画面(FirstScreen)
class FirstScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('Demo'),
      ),
      body: Center(

      // 中略
      child: RaisedButton(
          padding: EdgeInsets.all(12.0),
          shape: StadiumBorder(),
          child: Text(
            "次のページへ",
            style: TextStyle(fontSize: 20.0, color: Colors.white),
          ),
          color: Colors.green,
          onPressed: () {



            Navigator.push(
              context,
              new MaterialPageRoute<Null>(
                settings: const RouteSettings(name: "/second"),
                builder: (BuildContext context) {
                    return MaterialApp(
                      home: ScopedModel<DemoModel>(
                        model: quizBrainModel,
                        child: new SecondScreen(),
                      ),
                    );
                  }
              ),
            );

            /*
            ↑ScopedModelを使わない場合、
            Navigator.pushNamed(context, "/second");となっていた部分です。
            */

          },
        ),

//次の画面(SecondScreen)
class SecondScreen extends StatelessWidget {

  SecondScreen();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("Second Page"),
      ),
      body: 

      //中略

      new ScopedModelDescendant<DemoModel>(
         builder: (context, child, model) =>
         new Row( children: model.getSomeList())
     )

     /*
     ↑このページは、Scoped Model子孫クラスなので
     new ScopedModelDescendant<Model名称>で
     メソッドや変数が呼び出せます。
     例としてmodelで指定しておいた、
     特定のListを呼び出すメソッドを書いています。
     */

参考になればと思います。他にも書き方があるかもしれません。ご指摘などあればコメントいただければと思います。

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