20190830のAndroidに関する記事は16件です。

Flutter 1.7.x 環境をmacOS上に構築する

なぜこの文章を書いているか?

この文章(Flutter 1.2.x 環境をmacOS上に構築する)を過去に書き間が空いたのと再度Flutterを触り始めたので環境をまっさらな状態から作り直した作業ログとしてまとめたのが以下です。最近はLaravelとかNuxt.jsとかTypeScriptとかいじってますが、そろそろ他のものもということで。
今回はできる限り公式サイトの順番で環境を作っていこうと思います。
https://flutter.dev/docs/get-started/install/macos

環境(2019/08/30(金)現在)

  • macOS Mojave(10.14.6)
  • Xcode 10.3
  • IntelliJ IDEA Ultimate 2019.2.1
  • Flutter 1.7.8 + Hotfix 4 Stable

インストール

Tools

公式サイトに書かれている必須ツールは以下の通り。

  • bash
  • curl
  • git 2.x
  • mkdir
  • rm
  • unzip
  • which

このうち、macOS Mojaveに標準で入っていないのはgitなのでインストールする。

command
$ xcode-select --install

「"xcode-select"コマンドを実行するには、コマンドライン・デベロッパ・ツールが必要です。ツールを今すぐインストールしますか?」とウィンドウが表示されたらインストールをクリック。
「Command Line Tools使用許諾契約」画面が表示されたら内容を読んだ上で同意するをクリック。

インストールが完了したら以下でバージョンが表示されればOK。

command
$ git --version
git version 2.20.1 (Apple Git-117)

Flutter SDKのインストール

公式サイトのflutter_macos_v1.7.8+hotfix.4-stable.zipと書かれている青いボタンをクリックしてZipファイルをダウンロードします。
ダウンロードしたら、~/development以下に展開します。

command
$ mkdir ~/development
$ cd ~/development
$ unzip ~/Downloads/flutter_macos_v1.7.8+hotfix.4-stable.zip

~/development/flutter/bin/が作られるのでPATHを通します。

command
$ echo 'export PATH=$PATH:~/development/flutter/bin' >> ~/.profile
$ exec $SHELL -l

以下でバージョン情報が表示されればOK。

command
$ flutter --version
Flutter 1.7.8+hotfix.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 20e59316b8 (6 weeks ago) • 2019-07-18 20:04:33 -0700
Engine • revision fee001c93f
Tools • Dart 2.4.0

以下でバイナリをダウンロード(キャッシュ)しておきます。瞬間で終わるので実際に何か行われている感じはしないのですが、念の為。

command
$ flutter precache

Flutterの実行に必要な環境が揃っているか以下でチェックします。
途中「"gen_snapshot"はこのMac用に最適化されていないため、アップデートが必要です。」と出ますのでOKをクリックします。

command
$ flutter doctor -v
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Mac OS X 10.14.6 18G95, locale
    ja-JP)
    • Flutter version 1.7.8+hotfix.4 at /Users/foobar/development/flutter
    • Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
    • Engine revision fee001c93f
    • Dart version 2.4.0

[✗] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from:
      https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK
      components.
      (or visit https://flutter.dev/setup/#android-setup for detailed
      instructions).
      If the Android SDK has been installed to a custom location, set
      ANDROID_HOME to that location.
      You may also want to add it to your PATH environment variable.


[✗] Xcode - develop for iOS and macOS
    ✗ Xcode installation is incomplete; a full installation is necessary for iOS
      development.
      Download at: https://developer.apple.com/xcode/download/
      Or install Xcode via the App Store.
      Once installed, run:
        sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS and macOS platform side's plugin
        code that responds to your plugin usage on the Dart side.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/platform-plugins
      To install:
        brew install cocoapods
        pod setup
    ! Brew can be used to install CocoaPods.
      Download brew at https://brew.sh/.

[✗] iOS tools - develop for iOS devices
    ✗ libimobiledevice and ideviceinstaller are not installed. To install with
      Brew, run:
        brew update
        brew install --HEAD usbmuxd
        brew link usbmuxd
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller
    ✗ ios-deploy not installed. To install:
        brew install ios-deploy
    ! Brew can be used to install tools for iOS device development.
      Download brew at https://brew.sh/.

[!] Android Studio (not installed)
    • Android Studio not found; download from
      https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/setup/#android-setup for detailed
      instructions).

[!] Connected device
    ! No devices available

! Doctor found issues in 5 categories.

色々と未インストール(環境未整備)だと怒られているので順に入れていきます。

Xcodeをインストール

App Storeを起動し、検索窓にxcodeと入力して検索しインストールします。時間がかかるので私は一晩寝かしました。

すでにコマンドラインツールはインストール済みですが、公式ドキュメントのとおりにインストールしたXcodeから設定しておきます。

command
$  sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer

Xcodeのライセンス承諾を行うために以下のコマンドを実行します。Enterを押した後、Spaceキーで最後まで読み、入力を求められたらagreeとキー入力してEnterを押してライセンス条項に対し承諾をします。

command
$ sudo xcodebuild -license

この状態で以下のコマンドが実行できgitのバージョンが表示されればOKです。

command
$ git --version
git version 2.20.1 (Apple Git-117)

iOSシミュレータの設定

以下のコマンドを実行しiOSシミュレータを起動します。

command
$ open -a Simulator

Hardware > Device > Manage Devicesを選択し、「Install additional required components?」画面が出たらInstallをクリックし、パスワードの入力画面が出るのでMacのアカウントのパスワードを入力しOKをクリックします。

インストールが起動したらXcodeが起動するので、一旦iOSシミュレータを終了して再度起動します。私の環境ではiPhone XRのシミュレータが自動的にスタートしました。公式ドキュメントには「64bitのiPhone5S以降をセットアップしてね」とありますが現在のiOSシミュレータではこれ以上特に作業は必要ありません。

テストアプリを作ってみる

公式ドキュメントではここで唐突にテストアプリを作る項目が出てきます。iOSシミュレータがセットアップできているかなどのチェック用でしょうか?ここでは言われたとおりに作ってみることにします。

ターミナルで適当な場所に作業用ディレクトリを作成し、そこで作業を進めます。

command
$ mkdir ~/development/project
$ cd ~/development/project
$ flutter create my_app
$ cd my_app
$ flutter run
No connected devices.

Run 'flutter emulators' to list and start any available device emulators.

If you expected your device to be detected, please run "flutter doctor" to
diagnose
potential issues, or visit https://flutter.dev/setup/ for troubleshooting tips.

怒られました。どうやらiOSシミュレータを終了していたためのようです。iOSシミュレータを起動しiPhone XRのシミュレータが起動している状態でもう一度flutter runを実行してみます。
Flutter Demo Home Pageという画面がシミュレータに表示されればOKです。ターミナルでqを入力するとアプリが終了します。

iOSデバイス(実機)にデプロイするための設定

Homebrewをインストールするように書かれているのでそのとおりにします。Qiitaを読んでらっしゃるMacユーザならインストール済みと思われますので(偏見)説明は省きます。

command
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update

iOSデバイス(実機)にデプロイするために必要なツール群をbrewコマンドを使ってインストールしていきます。私の環境ではbrew link usbmuxdで「リンク済み」の旨のメッセージが出ましたが特に問題ないと思われます。

command
$ brew install --HEAD usbmuxd
$ brew link usbmuxd
$ brew install --HEAD libimobiledevice
$ brew install ideviceinstaller ios-deploy cocoapods
$ pod setup

先ほど作ったテストアプリをiOSデバイスにデプロイできるようにするためにプロジェクトにサインをします。基本公式ページのとおりですが簡単にまとめます。

  1. ターミナルでopen ios/Runner.xcworkspaceとしてXcodeを開きます
  2. iOSデバイスをMacに接続して「信頼するか?」など聞かれたら通常の方法でMacとiOSデバイスが通信できる状態にします
  3. Xcodeの左上のプルダウンがRunner > iPhone XRとなっていたら、プルダウンをクリックして接続したiOSデバイスを選択します
  4. 左ペインの一番上にあるRunnerプロジェクトをクリックして選択します
  5. Xcode 9と10は真ん中のペインのGeneral > Signing > Teamを、Xcode 11はSigning & Capabilities > TeamにあるTeamメニューに自分のチーム(Apple ID)を設定します
  6. 自分の環境では「You currently don't have access to this membership resource. To resolve this issue, agree to the latest Program License Agreement in your developer account.」と出ていたのでDev Centerにアクセス、その後accountにメニューから移動し「The Apple Developer Program License Agreement has been updated.」と赤背景でメッセージが出ているところから「Review Agreement」をクリック、内容を確認しチェックボックスをチェック、「I Agree」ボタンをクリック、最後にXcodeに戻ってエラーメッセージの下の「Try again」ボタンをクリックしました
  7. iOSシミュレータを終了した上でターミナルからflutter runを実行
  8. 先ほどと同じFlutter Demo Home PageがiOSデバイスで表示されたらOKです

Android Studioの代わりにIntelliJ IDEA Ultimateをインストールする

公式サイトではAndroid Studioをインストールしていますが、私はIntelliJ IDEAを普段使っているのでそちらで利用可能なように設定します。

まず、Homebrew Caskを使ってJetBrains ToolBoxをインストールします。

command
$ brew cask install jetbrains-toolbox

ApplicationからJetBrains ToolBoxを起動し、JetBrainsアカウントでログイン後、IntelliJ IDEA Ultimateをインストールします。
インストールが終わったらIntelliJ IDEAを起動します。初期設定の時にAndroid開発の追加インストールがされているはずですので、Welcome画面からCreate New Projectを選択し、New Project画面でAndroidを選択するとConfigure Android SDK画面が表示されるのでInstall SDKボタンをクリックします。
Nextをクリックしていき最後にFinishをクリックすると${USER_HOME}/Library/Android/sdk/以下にAndroid SDKがインストールされます。Choose your project画面に戻ったら今回はcancelをクリックしてキャンセルします。

Androidデバイスの設定

公式ページを見ながら順に設定していきます。

  1. Androidデバイスの開発者オプションUSBデバッグを有効にします。デバイスやOSのバージョンによりますが通常はビルド番号を何回かタップすると開発者オプションが有効になり、設定 > システム > 開発者オプションからUDBデバッグのオン・オフができます
  2. USBケーブルでMacとAndroidデバイスを接続します。
  3. ターミナルでfultter devicesを実行して接続したデバイスが1 connected device:などと表示されればOKです。表示されない場合やDevice XXXXXXXX is not authorized.と出ている場合は再接続やデバイスの認証、USB設定などを見直してみてください

この状態でターミナルからIntelliJ IDEAを起動し、Androidデバイスにデプロイしてみます。以下でターミナルから開けない場合は手動で~/development/project/my_app/androidをIntelliJ IDEAで開きます。

command
$ cd ~/development/project/my_app
$ idea ./android

最初はプロジェクトのインデックスに時間がかかります。インデックスが終わると私の環境では2つwarningが出ました。

  • Warning: License for package Android SDK Build-Tools 28.0.3 not accepted.
  • Warning: License for package Android SDK Platform 28 not accepted.

ライセンスについて承諾していないのが理由のようです。色々と調べるとAndroid SDKsdkmanagerで承諾することができそうです。とりあえず実行してみます。

command
$ ~/Library/Android/sdk/tools/bin/sdkmanager --licenses
No Java runtime present, requesting install.

おっとJava runtimeのインストールがまだでした。宗教上の理由でanyenvを使います。

command
$ brew install anyenv
$ anyenv install --init
$ echo 'eval "$(anyenv init --no-rehash -)"' >> ~/.profile
$ exec $SHELL -l
$ anyenv install jenv
$ exec $SHELL -l

$ brew tap caskroom/versions
$ brew cask install adoptopenjdk8
$ jenv add $(/usr/libexec/java_home -v 1.8)
$ jenv global 1.8
$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)

もう一度sdkmanagerを起動します。

command
$ ~/Library/Android/sdk/tools/bin/sdkmanager --licenses

色々とライセンスの承諾を聞かれるのでyを入力していきます。
最後にAll SDK package licenses acceptedと出ればOKです。

BuildタブからBuildを再実行してみるとandroid:successfulとなりWarningは消えました。

Run > Edit configurations...を選択し、左上の+をクリックしてAndroid Appを選択します。Namemy_appに変更、Moduleappを選択しApplyOKをクリックして画面を閉じます。
Run > Run 'my_app'を選択すると、どのデバイスをビルドターゲットにするか聞かれるのでUSBで接続したAndroidデバイスを選択しOKをクリックするとビルドが始まり、自動的にデバイスにデプロイされ、テストアプリが起動します。

Androidエミュレータの設定

IntelliJ IDEAからAndroidエミュレータが動作するように設定します。

  1. Tools > Android > AVD Managerを選択
  2. + Create Virtual Device...をクリック
  3. Phone > Nexus 5Xを選択しNextをクリック
  4. Pieの隣のDownloadをクリックしPieをインストールします
  5. Pieのインストールが終了したらFinishをクリック
  6. 改めてPieを選択しNextをクリック
  7. Finishをクリック
  8. ADV Managerを一旦閉じ、Run > Run 'my_app'を選択
  9. Select deployment target画面にNexus 5Xがあるので選択してOKをクリック
  10. エミュレータが自動で起動します
  11. エミュレータが起動すると自動的にデプロイされ、テストアプリが起動します

落ち穂拾い

fultter doctor -vを実行して問題を確認します。

command
$ flutter doctor -v
(中略)

[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/foobar/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-29, build-tools 29.0.2
    • Java binary at: /Library/Java/JavaVirtualMachines/adoptopenjdk-12.0.2.jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment AdoptOpenJDK (build 12.0.2+10)
    ✗ Android license status unknown.
      Try re-installing or updating your Android SDK Manager.
      See https://developer.android.com/studio/#downloads or visit https://flutter.dev/setup/#android-setup for detailed instructions.

(中略)

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/setup/#android-setup for detailed instructions).

[!] IntelliJ IDEA Ultimate Edition (version 2019.2.1)
    • IntelliJ at /Users/foobar/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • For information about installing plugins, see
      https://flutter.dev/intellij-setup/#installing-the-plugins

(中略)

! Doctor found issues in 3 categories.

Android SDKのライセンスが不明ということなので、flutter doctor --android-licenseを実行してみます。

command
$ flutter doctor --android-licenses
All SDK package licenses accepted.

Android Studioはわざといれていないのでここでは無視します。

IntelliJ IDEAFlutterDartのプラグインが入っていないと言われているのでインストールしてIntelliJ IDEAをリスタートします。

もう一度flutter doctor -vを実行します。

command
$ flutter doctor -v
(中略)
[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/setup/#android-setup for detailed instructions).
(中略)
! Doctor found issues in 1 category.

Android Studio以外のエラーが消えました。これで開発環境が揃ったことになります。

 まとめ

前回のインストールログをまとめてから、ちょっとしたサンプルプログラムを作っただけだったので、今度はもうちょっとちゃんとしたものを作りたいなぁと考えています(^^;

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

keystoreファイルのfingerprintの調べ方

コマンド

keystoreファイルがある場所へ移動し、下記を打ち込む。
keytool -list -keystore [file name]

$ keytool -list -keystore my-upload-key.keystore
Enter keystore password:
Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry

my-key-alias, Aug 28, 2019, PrivateKeyEntry,
Certificate fingerprint (SHA1): F5:A5:7E:6F:C2:CE:08

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore my-upload-key.keystore -destkeystore my-upload-key.keystore -deststoretype pkcs12".
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AsyncTaskLoaderは衰退したのか?

〇概要

久々にAsyncTaskLoaderを使ったらsupportLoaderManagerが非推奨になっていました。

'getter for supportLoaderManager: LoaderManager!' is deprecated. Deprecated in Java

loaderManagerも同じく

'getter for loaderManager: LoaderManager!' is deprecated. Deprecated in Java

〇調査

とりあえず公式
https://developer.android.com/reference/android/content/AsyncTaskLoader

目立つところに書いてますね。
warning.png

API level 28から非推奨と
たしかに
implementation 'com.android.support:appcompat-v7:28.0.0'
のバージョンを下げると警告は消えました。

Revisionsを見てみると

Revision 28.0.0 Productionは[September 21, 2018]からリリースなので1年ぐらい前ですね。

Revision 27.1.0 Release
には以下のようなことが
The underlying implementation of Loaders has been rewritten to use Lifecycle. While the API remains unchanged, there are a number of behavior changes:
ローダー関係アーキテクチャーライブラリを使うように書き換えるよと。

stackoverflowの投稿を読んでみると、アクティビティやフラグメントのライフサイクルの流れを考えると、ViewModelやLiveData等のアーキテクチャコンポーネントを使うほうが適切になったそうです。

もう利用頻度は大分減った技術なんですかね・・・

しかし、警告にあるようにSupport Libraryではまだ使えるます!
https://developer.android.com/reference/android/support/v4/content/AsyncTaskLoader.html

したがって修正

〇対応

XXX.kt
getSupportLoaderManager().initLoader(LOADER_TAG_METADATA,bundle,callback)

LoaderManager.getInstance(this).initLoader(LOADER_TAG_METADATA,bundle,callback)

loaderManager.destroyLoader(loader.id)

LoaderManager.getInstance(this@MainActivity).destroyLoader(loader.id)

〇結果

これで警告は解除。
しかし、やはり時代はAndroidのアーキテクチャーコンポーネントを自在に使いこなせるようにならないとダメみたいですね。

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

Bitriseのナンバリングを利用して、バージョンの自動インクリメントを実現(ReactNative, android)

下記記事でFastlaneをBitriseに組み込み、google play consoleへの自動デプロイを実現しました。
FastlaneのコマンドをBitriseに組み込む(android,ReactNative)
しかし、開発の現場ですとデプロイ毎にバージョン番号を繰り上げて管理したいです。
そこで利用するのが、Bitriseのナンバリング番号です。

ios版はこちら↓
Bitriseのナンバリングを利用して、バージョンの自動インクリメントを実現(ReactNative, ios)

Bitriseのナンバリング番号とは

Build numbering and app versioning
Bitriseのworkflowが動く際に管理されている番号のことです。
実行毎に番号が繰り上がるため、この番号をバージョン番号として使用しようと思います。

image.png

設定

android/app/build.gradleversionCodeを下記のように変更します。
Bitriseのデフォルトの環境変数であるBITRISE_BUILD_NUMBERを使用します。

android/app/build.gradle
defaultConfig {
        applicationId "com.reactnativeplatformBitrise"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion

        Integer buildNumb = System.getenv("BITRISE_BUILD_NUMBER") as Integer
        versionCode buildNumb ?: 1 

        versionName "1.0"
        ndk {
            abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
        }
    }

これに伴い下記記事で作成したファイルのincrementの機能は不要になるため、該当部分を削除します。
fastfileの編集

android/fastlane/Fastfile
  desc "submit internal release to Google Play Store"
  lane :internal do

    # 下記のincrement機能は削除
    # increment_version_code(
    #   gradle_file_path: "./app/build.gradle"
    # )

    # make apk file
    gradle(task: "assembleRelease")
    # find the apk file
    supply(
      track: "internal",
      apk: "#{lane_context[SharedValues:: GRADLE_APK_OUTPUT_PATH]}"
    )

実行

上記設定でBitriseが動きますと、google play consoleにてバージョン番号がBitriseのナンバリング番号と一致しているのが確認できます。
image.png

参考

Adding Bitrise build number to Android app version Code

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

あなたのToolbarはカスタム or 標準

発信したいこと

久々の投稿でございます.

この記事を書くに至ったのは複数のAndroidプロジェクトを見ていて
Toolbarの実装方法が異なりどちらが実装しやすいのかなと思い投稿させていただきました。
やり方は様々あるのであくまで一つのやり方として参考にしてもらえれば幸いです!

環境

Mac : 10.14.3
AndroidStudio : 3.4.2
Language : Kotlin

実装

カスタムのxml

Toolbarの中にゴリゴリ、レイアウトを実装しています.

<android.support.v7.widget.Toolbar
        android:id="@+id/toolbar1"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary">

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <ImageView
                android:id="@+id/navigationImageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_arrow_back_24dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                tools:ignore="ContentDescription"/>

        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/title"
                android:layout_marginStart="28dp"
                android:textSize="20sp"
                android:textColor="@color/white"
                app:layout_constraintTop_toTopOf="@+id/navigationImageView"
                app:layout_constraintBottom_toBottomOf="@+id/navigationImageView"
                app:layout_constraintStart_toEndOf="@+id/navigationImageView"/>

        <ImageView
                android:id="@+id/menuImageView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_settings_24dp"
                android:layout_marginEnd="20dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                tools:ignore="ContentDescription"/>

    </android.support.constraint.ConstraintLayout>

</android.support.v7.widget.Toolbar>

標準のxml

スッキリして見やすいですね!

<android.support.v7.widget.Toolbar
            android:id="@+id/toolbar2"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimaryDark"
            app:title="@string/title"
            app:titleTextColor="@color/white"
            app:navigationIcon="@drawable/ic_arrow_back_24dp"/>

所感

カスタム

  • メリット
    自由自在にレイアウトできる。 iOSと同じようにして欲しい要望に答えることができる
  • デメリット
    複数エンジニアが開発している場合、全画面のToolbarを同様にすることが難しい

標準

  • メリット
    コードを初見で理解できる
  • デメリット
    特に見当たらない

コード見たい人はココ

https://github.com/m-saeki0926/toolbar_sample_android

参考

https://developer.android.com/reference/android/support/v7/widget/Toolbar

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

Google play consoleへデプロイする際のエラー[not compliant with the Google Play 64-bit ](ReactNative)

エラー

Google play consoleへアプリをデプロイ(.apkファイルのアップロード)の際に、下記のようなエラーが発生しました。

This release is not compliant with the Google Play 64-bit requirement

解決法

android/app/build.gradleのndk部分を下記書き換えた上で.apkファイルの発行->再度、google play consoleへアップロード

    defaultConfig {
        applicationId "jp.co.hogehoge"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        ndk {
            abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
        }
    }

参考

Building with Android Studio or Gradle

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

Google play consoleへアプリをデプロイする際のエラー[must target at least api level 28](ReactNative)

エラー

Google play consoleへアプリをデプロイ(.apkファイルのアップロード)の際に、下記のようなエラーが発生しました。

must target at least api level 28

解決法

android/build.gradleの下記を28に書き換えた上で.apkファイルの発行->再度、google play consoleへアップロード

buildscript {
    ext {
        targetSdkVersion = 28

参考

Meet Google Play's target API level requirement

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

Google play consoleへデプロイする際のエラー[must target at least api level 28](ReactNative)

エラー

Google play consoleへアプリをデプロイ(.apkファイルのアップロード)の際に、下記のようなエラーが発生しました。

must target at least api level 28

解決法

android/build.gradleの下記を28に書き換えた上で.apkファイルの発行->再度、google play consoleへアップロード

buildscript {
    ext {
        targetSdkVersion = 28

参考

Meet Google Play's target API level requirement

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

#7 Kotlin で TODO アプリを開発・リリースするまでの記録

前回のあらすじ

前回までの記事

#1 Kotlin で TODO アプリを開発・リリースするまでの記録
#2 Kotlin で TODO アプリを開発・リリースするまでの記録
#3 Kotlin で TODO アプリを開発・リリースするまでの記録
#4 Kotlin で TODO アプリを開発・リリースするまでの記録
#5 Kotlin で TODO アプリを開発・リリースするまでの記録
#6 Kotlin で TODO アプリを開発・リリースするまでの記録

前回のあらすじ

  • リストに区切り線ができた
  • タイトルを設定した

今回の目標

  • RecyclerView でデータの追加・削除

RecyclerView でデータの追加・削除

#2 を見返して、RecyclerView を調べた際の参考記事をまねします。
まねする記事はこちらにしました。

RecyclerViewと仲良くなる

理由は次の通りです。

  • 表示されているリストに追加、削除ができること
  • 一時的なデータ保存なので、model などの要素をいったん除外して、目的に絞って作成できそう

それでは早速進めていきます。

Android Support Design Library

design ライブラリをインポートします。
最新のバージョンをインポートするため、#4 の手順に倣って行います。
#4 Kotlin で TODO アプリを開発・リリースするまでの記録
image.png
design でライブラリ検索します。
選択肢に AndroidX がないようですが、こういうものなのでしょうか?
「OK」をクリックして反映させます。

build.gradle(app)
dependencies {
    implementation 'com.android.support:design:28.0.0'
}
Version 28 (intended for Android Pie and below) is the last version of the legacy support library, so we recommend that you migrate to AndroidX libraries when using Android Q and moving forward. The IDE can help with this: Refactor > Migrate to AndroidX... less... (Ctrl+F1) 
Inspection info:There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion).
"Issue id: GradleCompatible

言われた通りにやっただけなのにエラーが発生。

Refactor > Migrate to AndroidX
AndroidX に変換してください、みたいなことを指示されている気がします。
いや、AndroidX が選択肢にあれば選択してましたけど・・・

それに Migrate to AndroidX は、個別に変換するのではなく全体変換するはずです。
変にほかのソースで手直しが発生したら嫌なので試したくないなぁ、と思って、他の方法を調べてみます。

小さな Android アプリを AndroidX に置き換えてみる
大体は com.android.support -> androidx でいけそうです。

ほんとだ・・・

build.gradle(app)
dependencies {
    implementation 'androidx.design:28.0.0'
}

書き直したらエラーがなくなりました!
それなら最初から AndroidX を選択肢に入れておいてくれ。

ビルド実行

本当はもう少し後に気づきましたが、紛らわしいのでここに書いておきます。

ビルドを実行しようとしたら、エラーになりました。

ERROR: Failed to resolve: androidx.design:28.0.0:
Affected Modules: app

androidx.design:28.0.0 が解決できないと言われてる・・・

build.gradle(app)
dependencies {
    implementation 'com.android.support:design:28.0.0'
}

当初設定したライブラリに切り戻してビルドしたところ、無事実行できました。
エラーは相変わらず出ていますが、ビルドが実行できるので問題なしとします。

なんでなんや

Material Icons からボタン素材を追加する

Material Icons とは

【Google提供】マテリアルアイコンの使い方とカスタマイズ方法まとめ
Material Icons(マテリアルアイコン)とは、Googleが2014年に提唱したMaterial Design(マテリアルデザイン)です。
ざっくり言うと、どのデバイスでもデザインを統一させて、使いやすくしていきましょうという動きですね。

数多あるフリーアイコンの中で、Material Icons を選ぶのはほかでもなく、Android Studio から直接挿入できる利便性です。
後は Google 先生の出したデザインなので間違いないです。すりすり。

+ - ボタン素材を追加する

アプリ内ボタンにマテリアルデザインアイコンを使う [Android]

さくっとボタン素材となるアイコンイメージを追加しました。

  • ic_add_black_24dp.xml
  • ic_remove_black_24dp.xml

Item のレイアウトを作成

1 行に対するレイアウトは、これまでの作業の中で list_item.xml として作成しています。
基本仕様ではテキスト欄が 1 つだけなので、TextView は 1 つで良さそうです。

ビルド実行

image.png

いいかんじ!

list_item.xml

変更したソースコードはこちらです。

list_item.xml
<androidx.cardview.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardElevation="2dp"
        android:foreground="?android:attr/selectableItemBackground">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal"
            android:paddingStart="25dp"
            android:paddingEnd="0dp"
            android:gravity="center_vertical">

        <TextView
                android:id="@+id/itemTextView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"/>

        <ImageButton
                android:id="@+id/itemImageButton"
                android:layout_width="70dp"
                android:layout_height="match_parent"
                android:background="#000f"
                card_view:srcCompat="@drawable/ic_remove_black_24dp"
                android:contentDescription="@string/app_name"/>

    </LinearLayout>
</androidx.cardview.widget.CardView>

ImageView がなくなり、ImageButton が入った以外にも、レイアウトを整えるためいろいろ変更しました。
ワーニングが発生したため、追加したプロパティもあります。

LinearLayout

  • android:paddingStart="25dp"

左端からテキスト開始位置をずらすために追加しました。
CSS では padding-left です。

  • android:paddingEnd="0dp"

レイアウト的には paddingStart のみで問題なかったのですが、paddingStart を入れるなら paddingEnd も入れるようにとのワーニングが発生したため追加しました。
値を正数にするとボタン位置が右端からずれてしまうので、0 指定です。
CSS では padding-right です。

TextView

  • android:layout_width="0dp"

当初は 0 dp ではなく wrap_content を指定していましたが、次のワーニングが出たので変更しました。

Use a layout_width of 0dp instead of wrap_content for better performance

横幅を指定しないことになりますが、次項の layout_width が横幅を担います。

  • android:layout_weight="1"

layout_weightの使い方(比率で画面を構成する)

View に対してアイテムの表示範囲を比率で指定できます。
CSS では flex-grow のようなものだと思っています。

View に対してアイテムが横並びになっているとします。
以下のように割り当てられるイメージです。

100 dp の View の場合
[layout_weight="2"] → 20dp
[layout_weight="8"] → 80dp

200 dp の View の場合
[layout_weight="2"] → 40dp
[layout_weight="8"] → 160dp

layout_weight="1" を指定し、もうひとつの値を固定値にすれば、一方のみレスポンシブ対応が可能です。

100 dp の View の場合
[layout_width="20dp"] → 20dp
[layout_weight="1"] → 80dp

200 dp の View の場合
[layout_width="40dp"] → 40dp
[layout_weight="1"] → 160dp

今回はボタンのサイズを変えずに、テキストの長さがレスポンシブになるデザインが望ましいと考えたので、layout_weight="1" にしました。

レスポンシブデザインの考え方としては以下の通りです。

【図解】レスポンシブデザインとは?定義、特徴、メリットとデメリットを解説

マルチスクリーンではないので必要なのかわかりませんが、意識して適用したのでここに記載しておきます(アプリより WEB 的な考え方だと思います)。
この考え方でなくとも、長さを可変にしたい場合に最適です。

ImageButton

  • android:layout_width="70dp"

前述の通りです。
現在の View サイズから鑑みて、固定値を割り当てました。

  • android:layout_height="match_parent"

リストの縦幅に対して最大値にしています。
理由は誤タップを防ぐためです。

  • android:contentDescription="@string/app_name"

Image には Description を指定するようにとワーニングが出たので追加しました。
最初は文字列にしようとしましたが、@string を挿入するようにとのワーニングが出て、@string と入力すると補完候補が表示されたため、流されるまま @string/app_name を指定しました。

Android Studio が全部導いてくれます。
そして次第にワーニングに踊らされている気分になっていく。

余談:paddingStart と paddingLeft

  • paddingStart と paddingEnd
  • paddingLeft と paddingRight

これらは基本的に同じ動作のようです。
前者は左からの余白、後者は右からの余白を指定します。
違いとしては次の記事をお読みください。

多言語化を見据えた開発 StartとLeftの違いとは | Development aiming at multilingualization, Difference between Start and Left | Android

文字が左から開始する言語、右から開始する言語に対応できるように、左右ではなく文字の開始位置という概念で使用とするようです。
そもそもソース上で Left を書いても、Start を使用するようにとワーニングが発生するので、大人しく従いましょう。

Consider adding android:paddingStart=“25dp” to better support right-to-left layouts Error

RecyclerViewHolder.kt

ImageView を ImageButton に差し替えます。

RecyclerViewHolder.kt
import android.widget.ImageButton

val itemImageView: ImageView = view.findViewById(R.id.itemImageView)

val itemImageButton: ImageButton = view.findViewById(R.id.itemImageButton)

import 構文は自動挿入です。
自動挿入設定は #4 の記事で行っています。

RecyclerAdapter.kt

RecyclerAdapter.kt
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
    holder?.let {
        it.itemTextView.text = itemList.get(position)
    }
}

ImageButton の Image は list_item.xml で直接指定しています。
ここで記載する必要がなかったので消しました。

まとめ

今回はここまで。
ボタンは動きませんが、レイアウト追加ができました。
目標到達までまだまだのようです。
冒頭部分で、こんなに記事が長くなるとは思いませんでしたが・・・。

成果

  • リストに削除ボタンを設置した(イメージ)

参考記事に感謝

たくさんの記事を引用させて頂きました。ありがとうございます。
著者が一部のみを引用したことにより、引用元の意図しない情報として受けられることがないよう、できれば引用元の記事を読んでいただけますようお願いいたします。
またいずれの記事の感想も、著者個人の意見に基づくものですのでご注意ください。

本記事で参考にさせて頂いたこちらの記事に、改めて特別な感謝を。

RecyclerViewと仲良くなる

次の記事

#8 Kotlin で TODO アプリを開発・リリースするまでの記録

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

apkファイル(android)を実機にインストールする方法(ReactNative)

手順

デバイス(OSがandroid)をお使いのコンピュータに接続します。
その後、apkファイルがある場所まで移動し、下記を実行します。

adb install sample.apk

エラー

$ adb install sample.apk
* daemon not running; starting now at tcp:5037
* daemon started successfully
error: device unauthorized.
This adb server's $ADB_VENDOR_KEYS is not set
Try 'adb kill-server' if that seems wrong.
Otherwise check for a confirmation dialog on your device.

解決法

デバイスをコンピュータから外します。
andoridのSystemsへ移動し、Developer options -> USB debuggingをon, Revoke USB debugging authorizationsをタップします。

ターミナルにて、下記コマンドを実行

adb kill-server
adb start-server

参考

How To Fix ADB Device Unauthorized Message On Android

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

AndroidX + Robolectric + MultiDex のビルドがエラーになるとき

Robolectric の MultiDex 対応に org.robolectric:shadows-multidex をいれてて
AndroidX への移行している場合、
ビルド時に Jetifier まわりでエラーが出ることがあります:

> Execution failed for JetifyTransform: /home/circleci/.gradle/caches/modules-2/files-2.1/org.robolectric/shadows-support-v4/3.3.2/120e08a6d6d9bd56614f9a3e91c853039107761d/shadows-support-v4-3.3.2.jar.
> Failed to transform '/home/circleci/.gradle/caches/modules-2/files-2.1/org.robolectric/shadows-support-v4/3.3.2/120e08a6d6d9bd56614f9a3e91c853039107761d/shadows-support-v4-3.3.2.jar' using Jetifier.
Reason: The given artifact contains a string literal with a package reference 'android.support.v4.content' that cannot be safely rewritten. Libraries using reflection such as annotation processors need to be updated manually to add support for androidx.

ブラックリストに追加

この場合 Jetifier と相性が悪いので
Workaround としてブラックリスト入りさせる方法が提案されてます:

? https://github.com/robolectric/robolectric/issues/4491#issuecomment-498603341

gradle.properties
android.jetifier.blacklist=shadows

ShadowAndroidXMultiDex を使うように変更

あとは Config を与えるときの Config.Builder.setShadows(ShadowMultiDex.class)Config.Builder.setShadows(ShadowAndroidXMultiDex::class) に変更しておくのも必要です。

バージョンによっては ShadowAndroidXMultiDex がないことがあるので、shadows-multidex のバージョンを上げておくことも必要です:

app/build.gradle
testImplementation "org.robolectric:shadows-multidex:4.3"

GlobalConfigProvider の設定

で、この Config を設定するために GlobalConfigProvider を設定しておく必要があります:

? https://github.com/robolectric/robolectric/issues/4770#issuecomment-479559317

app/src/test/java/your/own/app/provider/CustomGlobalConfigProvider.kt
package your.own.app.provider

import com.google.auto.service.AutoService
import org.robolectric.annotation.Config
import org.robolectric.pluginapi.config.GlobalConfigProvider
import org.robolectric.shadows.multidex.ShadowAndroidXMultiDex

@AutoService(GlobalConfigProvider::class)
class CustomGlobalConfigProvider: GlobalConfigProvider {
    override fun get(): Config {
        return Config.Builder
                .defaults()
                .setShadows(ShadowAndroidXMultiDex::class.java)
                .setPackageName("your.own.app")
                .build()
    }
}

AutoService を使ってるので以下を追加します:

app/build.gradle
testImplementation "com.google.auto.service:auto-service:1.0-rc6"
kaptTest "com.google.auto.service:auto-service:1.0-rc6"

これで Robolectric の Multidex が Jetifier 有効でもビルドできるようになるかと思います。

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

中国でAndroidStudioのハンズオンの準備

ideagearの河野です。

中国でAndroidアプリのハンズオンなどを開催しようとすると、Google関連が遮断されているためいろいろ辛いです。

ここではそういったあれこれをTipsとしてまとめます。

ターゲットは主に Microsoft Windows 10 64bit です。

基本的なツール

Google Chrome

https://google.cn/chrome/ からダウンロードできます
image.png

7zip

https://www.7-zip.org/

Windowsに入っているZIP機能は速度が遅かったり長いパスで問題が出たりするのでこれをインストールします。アーカイバはいろいろありますが、ハッシュチェック機能もあるこれを使います。

AndroidStudio

「Windows版Android Studioをzipアーカイブからセットアップする」
https://qiita.com/keicha_hrs/items/070b8f32fc98157541b2
をお手本にしてzip版をダウンロードします。

本来は、developer.android.com/studiostudioからダウンロードだけれども、グレートファイアウォールのおかげでアクセスできない。
代わりにhttps://developer.android.google.cnからダウンロード。

image.png

その他のサイトも見つけましたが・・・

http://www.android-studio.org/
image.png
はて。これは何でしょう。ダウンロードできそうですが3.4.1で古そうです。
https://www.androiddevtools.cn/
image.png
ここは新しいしダウンロード速度も速かったです。

当然、ダウンロードしたものはハッシュタグで正当性をチェックしてください。

セットアップ

Windowsキー+Sを押して、CMDとタイプインしてコマンド画面を呼び出して、そこに表示されているコマンドプロンプトが英数字だけであることを確認します。

image.png

もし漢字やスペースなどが入っていたら英数字だけのユーザを作成するか、以下の展開場所を工夫してください。

今回ダウンロードしたのは
Windows (64-bit) android-studio-ide-191.5791312-windows.zip
No .exe installer 723 MB 201328aa7b2593ad538f0cc5d2d98237435d56c89d2bb08f072391de5faaf9f1

です。

7-zipでSHA-256をチェックした後展開します。

「Windows版Android Studioをzipアーカイブからセットアップする」
https://qiita.com/keicha_hrs/items/070b8f32fc98157541b2
を元にインストールしていきます。

途中、
image.png

となりましたがCancelを選びました。

コミュニュケーションチャンネル

WeChatアプリ

image.png

中国語・日本語・英語が飛び交います。WeChatは翻訳が使いやすいです。また遠隔参加の方もWeChatでコンタクトを取ります。

Slack

Slackは遅いときもありますがおおむね中国国内でも使えます。

Wiki

ハンズオンの情報の集積地としてMediaWikiサーバーを立てました。
サーバはシンガポールリージョンのAWSで用意しました。

サーバ連携

クラウドを使うので、AWSに建てたサーバにアクセスします。

putty

https://www.putty.org

からputtyをダウンロード、インストールしてください。

Raspberry Pi

開催場所によってはクラウドサーバへの回線が細かったので、急遽 RaspberryPi をローカルLANに接続してクラウドサーバの代わりとしました。

Android Studio 起動して設定

起動した後も、何かとダウンロードが行われて時間がかかります。ここの説明の分を事前に行っておきます。

最初のプロジェクトをビルド

image.png

image.png

WorkshopTest2とありますが、実際にはWorkshopTest1としてください。

image.png

image.png

Workshoptest1:syncing...

と出ています。これが終わるまでしばらく待ちます。

その後、「Error running app No target device found.」と出て止まります。

Kotlinエラーを直す

プロジェクトの設定によっては下記のようにエラーが出ます。

image.png


org.gradle.internal.exceptions.LocationAwareException: New Gradle Sync is not supported due to containing Kotlin modules
    at org.gradle.initialization.exception.DefaultExceptionAnalyser.transform(DefaultExceptionAnalyser.java:99)
    at org.gradle.initialization.exception.DefaultExceptionAnalyser.collectFailures(DefaultExceptionAnalyser.java:65)
    at org.gradle.initialization.exception.MultipleBuildFailuresExceptionAnalyser.transform(MultipleBuildFailuresExceptionAnalyser.java:47)
    at org.gradle.initialization.exception.StackTraceSanitizingExceptionAnalyser.transform(StackTraceSanitizingExceptionAnalyser.java:29)
    at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:174)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:165)
    at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:134)
    at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:58)
    at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:55)
    at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:82)
    at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:75)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:183)
    at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
    at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:75)
    at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:55)
    at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:60)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:58)
    at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
    at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:39)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:51)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:45)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
    at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
    at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:45)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:49)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:46)
    at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:78)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:46)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:31)
    at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:42)
    at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:28)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:78)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:52)
    at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:59)
    at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:36)
    at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:68)
    at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:38)
    at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:37)
    at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:26)
    at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
    at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
    at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:60)
    at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:32)
    at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
    at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
    at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:48)
    at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:32)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
    at org.gradle.util.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:62)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:81)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:295)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: com.android.tools.idea.gradle.project.sync.ng.NewGradleSyncNotSupportedException: New Gradle Sync is not supported due to containing Kotlin modules
    at com.android.tools.idea.gradle.project.sync.ng.SyncProjectModels.failIfKotlinPluginAppliedAndKotlinModelIsMissing(SyncProjectModels.java:131)
    at com.android.tools.idea.gradle.project.sync.ng.SyncProjectModels.failIfKotlinPluginAppliedAndKotlinModelIsMissing(SyncProjectModels.java:136)
    at com.android.tools.idea.gradle.project.sync.ng.SyncProjectModels.populate(SyncProjectModels.java:88)
    at com.android.tools.idea.gradle.project.sync.ng.SyncAction.execute(SyncAction.java:59)
    at com.android.tools.idea.gradle.project.sync.ng.SyncAction.execute(SyncAction.java:33)
    at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:80)
    at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner$ActionRunningListener.runAction(ClientProvidedPhasedActionRunner.java:120)
    at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner$ActionRunningListener.run(ClientProvidedPhasedActionRunner.java:110)
    at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner$ActionRunningListener.projectsEvaluated(ClientProvidedPhasedActionRunner.java:98)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:376)
    at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:358)
    at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:58)
    at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:346)
    at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:333)
    at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:42)
    at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:230)
    at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:149)
    at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:58)
    at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:324)
    at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:234)
    at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:140)
    at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy13.projectsEvaluated(Unknown Source)
    at org.gradle.initialization.DefaultGradleLauncher$NotifyProjectsEvaluatedListeners.run(DefaultGradleLauncher.java:407)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
    at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
    at org.gradle.initialization.DefaultGradleLauncher.projectsEvaluated(DefaultGradleLauncher.java:428)
    at org.gradle.initialization.DefaultGradleLauncher.access$1400(DefaultGradleLauncher.java:50)
    at org.gradle.initialization.DefaultGradleLauncher$ConfigureBuild.run(DefaultGradleLauncher.java:305)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
    at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
    at org.gradle.initialization.DefaultGradleLauncher.configureBuild(DefaultGradleLauncher.java:210)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:151)
    ... 75 more

と出ています。

image.png

「File」-「Settings」-「Kiitlin」の開くと「A new version ・・・・」と出ているので「Install」とします。
Installが終わったら一度AndroidStudioを終了してもう一度起動しなおしたら治りました。

ADV(エミュレーター)を用意

PCのメモリが8GBytes以上ある場合は、エミュレーターを使えるようにしておきます。
「tools」-「AVD Manager」で設定します。

image.png

「+ Create Virtual Device...」を押します

image.png

Pixel 2を選ぶことにします。

image.png

8.1のOreoを選ぶことにしましたが、一番最新のものでも良かったかも知れません。「Download」を選びます。
image.png

Acceptを押してNext
image.png

ダウンロードに時間がかかります。その後続けていくとADV設定が完了し、エミュレーターが起動します

image.png

エミュレーターは最初の起動は時間がかかります。

image.png

実行できました。

実機を接続する

USBケーブルを用意し、Android実機を接続します。

「開発者向けオプション」で「USBデバッグ」を有効にします。

Android 8.0以降の場合は、設定画面から「システム」-「端末情報」-「ビルド番号」を7回タップすると「システム」-「開発者向けオプション」が表示されるようになる。
英語メニューの場合は、設定画面から[System]- [About phone]- [Build number] を 7 回タップすると、

[System]- [Developer options] が表示されるようになる。

Android8.0未満の場合は、機種ごとに違うので、要調査。

実機を選択し、Run-Runすると起動します。
image.png

このような表示になった場合は、プロジェクトのSDKバージョンの設定に比べて実機のAndroidバージョンが低いために起こっています。

image.png

この場合はエミュレーターを使って開発するか、以下のようにSDKのバージョンを適宜ダウンロードし、プロジェクトを作り直します。

image.png

開発を始めてから

ビルド時に、gradleがsyncのためにインターネット接続を行うので、回線が遅い場合はgradleをオフラインモードに変更しておくのがいいかもしれません。
「File」-「Settings」-「Build,Excusion,Deployment」-「Gradle」の「Offline work」にチェックを入れます。

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

Firebase Test Labのcloudtestingscreenshotter_libでスクリーンショット名に日本語を使ったらダメだった件

Firebase Test Labでスクリーンショットを撮るために cloudtestingscreenshotter_lib というライブラリが提供されています。(ライブラリのDL、導入方法などは下記公式ページへ→ Firebase Test Lab インストゥルメンテーション テストのスクリーンショットを作成する

ScreenShotter.takeScreenshot("main_activity", activity)

↑のようなコードでスクリーンショットを撮ることができるのですが、引数で指定しているスクリーンショット名(上記のコードでは "main_activity")に日本語を使ったらダメだったというお話です。

何故かテストが「不確定」となって終わってしまう

Firebase Test Labを定期実行させているのですが、いつぞやのタイミングから「不確定」というステータスでテストが終わってしまうようになりました。

スクリーンショット 2019-08-30 8.04.14.png

CI上のログから ERROR: (gcloud.firebase.test.android.run) Firebase Test Lab infrastructure failure: Exhausted test run attempts というエラーメッセージが出ており、Freeプランで使っているから何か制限をオーバーしたのかな?と思ったりしたので、数日様子見。
しかし数日経っても状況は変わらずで、Consoleからもこれ以上の情報を得ることができず、途方にくれてました。

Firebase Slackコミュニティで質問してみた

どうしようもないので、「明確な結果が得られずお困りの場合は、Firebase Slack コミュニティの #test-lab チャンネルにご参加ください。」 とあったのでSlackで質問してみました。

Firebase Community Slackには、各サービスごとにチャンネルがあり、そこの #test-lab で質問をしました。

内容としては

ERROR: (gcloud.firebase.test.android.run) Firebase Test Lab infrastructure failure: Exhausted test run attempts というエラーが出てしまっています。フリープランで使ってるけど、上限はオーバーしてないんだけど :thinking:

みたいな感じです。(ちなみにめっちゃ拙い英語で送りました:baby:

Googlerから返信が来た :smile:

1日もしないうちに返信が来ました。
内容としては

「スクリーンショット名をasciiにして、もっかい試してほしい」

みたいな感じで、 This is the error we see on our side: (エラー文) とFirebase側のエラーの内容も添付してくれました。(SlackのDM)

エラーの内容を見ると、いつぞやのタイミングで追加したテストで、スクリーンショット名に日本語が含まれているやつが原因っぽいことがわかりました。

スクリーンショット名はASCII文字で

すぐさまスクリーンショット名に日本語を指定しているところを直して、Firebase Test Labでテストを走らせたら、問題なく実行されました。
スクリーンショット名には日本語など含ませず、ASCII文字でやる必要がある ということがわかりました。

まとめ

  • cloudtestingscreenshotter_lib でスクリーンショットを撮るときは、スクリーンショット名に日本語を指定してるとダメだった:warning:
  • FirebaseでわからないことがあったらSlackで質問をするとGooglerが返信してくれる:relaxed:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidのIMEI取得

echo "[device.imei]: [$(adb shell service call iphonesubinfo 1 | awk -F "'" '{print $2}' | sed '1 d'| tr -d '\n' | tr -d '.' | tr -d ' ')]"

参考)https://stackoverflow.com/questions/6852106/is-there-an-android-shell-or-adb-command-that-i-could-use-to-get-a-devices-imei/37940140#37940140

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

[SQLite] Cursorで起こるIllegalStateException

私が開発したアプリ KUMIWAKE のユーザー様から以下のようなお問い合わせをいただきました。

以前まで普通に使えていましたが、メンバーを選んだり組分けしようとすると「問題が発生したためkumiwakeを終了します」のと出ます。どうすればよいですか?

はい…完全にこちらの不手際でユーザー側ではどうにもできないので至急アップデートにて対応させていただきました。最新ver.でなぜかクラッシュが多かったのでこういったご意見は本当に助かります。

エラーの原因

しかし、ここで困ったことが1つ。
自分の環境ではエラーが再現できないのです。実機もAVDもだめ。

Play Consoleのスタックトレースを見ると以下のようにありました。

java.lang.IllegalStateException: 
  at android.database.CursorWindow.nativeGetString (CursorWindow.java)
  at android.database.CursorWindow.getString (CursorWindow.java:438)
  at android.database.AbstractWindowedCursor.getString (AbstractWindowedCursor.java:51)
  ...

調べてみるとデータベースのCursorによるエラーのようでした。

以前は機能していたようなので、Cursor部分の変更履歴を見直して何とか原因を特定しました。

c.getString(c.getColumnIndex(COL_NAME))←この部分です

https://codeday.me/jp/qa/20190421/668862.html
こちらの方の回答にあるようにgetColumnIndex(COL_NAME)がCOL_NAMEのIndexを見つけられず、-1を返していたようです。それによってc.getString(-1)となり、カラムが見つからない!とエラーを吐いたわけですね。(カラム番号は0~)

解決策

とりあえずは以前の状態を復元するためにgetColumnIndex()を使わない方向で落ち着きました。(分かりやすいので本当は使いたいですが...)

修正前:c.getString(c.getColumnIndex(COL_NAME))
修正後:c.getString(1)

直接カラム番号を指定しました。
修正後は正常に動作したようです。めでたしめでたし。

getColumnIndex(COL_NAME)を使いたい場合は、カラムが見つからないときに-1が返されてしまうので、こちらの記事にあるようにgetColumnIndexOrThrow(COL_NAME)を使うのが良さそうです。

疑問は残る

https://teratail.com/questions/128793
同じような問題で悩んでいる方がおられました。この方の質問にあるように端末によってgetColumnIndex()がうまく値を返す場合と返さない場合があるようです。Androidのバージョンには依存していなかったので、端末の問題なのか...
SQLiteまわりの詳しい方おられましたらご教授をお願いします。(泣)

参考

Android Developers
Yukiの枝折 Android:CursorのAPIまとめ

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

Flutterウィークリー #73

Flutterウィークリーとは?

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

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

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

読み物&チュートリアル

Flutterチュートリアル:従来のショッピングアプリUIをフラッターで簡単に作成

https://cybdom.tech/flutter-tutorial-shopping-app-ui/


新しいUIチュートリアル、今回はショッピングアプリ。

Flutter Inを使用したFlutter GridViewチュートリアル

https://www.gotut.net/flutter-grid-view-tutorial/


FlutterのGridviewを使用して、フェードイン効果のある画像ギャラリーを作成する方法を学びます。

Flutter電卓アプリを構築する

https://itnext.io/building-a-calculator-app-in-flutter-824254704fe6


Kenneth Reillyは、 Flutter段階的に電卓を作成します。

Flutter iOSアプリのキーボード完了ボタンUX

https://blog.usejournal.com/keyboard-done-button-ux-in-flutter-ios-app-3b29ad46bacc


Ramankit Singhは、iOSのキーボードで[完了]ボタンを再現するソリューションを示しています。

ストリームを使用したFlutterでの非同期プログラミング

https://medium.com/@bertoboh/async-programming-in-flutter-with-streams-c949f74c9cf9


Albert ObohによるFlutterでのStreamsの使用の概要

Flutter認証

https://medium.com/@greg.perry/auth-in-flutter-97275b29b550


Greg Perryは、Firebase authでauthパッケージを使用する方法を詳細に分析して示します。

FlutterとCustom Painterを使用してレーダーチャートを作成する

https://medium.com/@d.panaite92/building-a-radar-chart-with-flutter-and-custom-painter-384c005002f9


Dan Panaiteによるこのチュートリアルで、カスタムペインターを使用して独自のレーダーチャートを作成する方法を学びます。

Flutter for the Web — Githubにデプロイする

https://medium.com/flutter-community/flutter-for-the-web-deploy-to-github-da454e4bc079


Flutter webを使用していますか?アコラ・イングのおかげで、あなたの作品を世界に見せてください。 DKB。GithubPagesにデプロイする方法を教えてくれます。

Flutter iOSアプリのアイコンをプログラムで変更する

https://medium.com/flutter-community/programatically-change-ios-app-icon-in-flutter-c9e84bc541a2


iOS 10.3以降、Appleはアプリのランチャーアイコンをプログラムで変更する機能を導入しました。 Alessandro FaveroはFlutterからそれを行う方法を説明しています。

MVCの神社

https://medium.com/flutter-community/shrine-in-mvc-7984e08d8e6b


MaterialサンプルアプリShrineを知っていますか? Greg Perryが、サンプルを取得してMVCアーキテクチャに変更する方法を説明します。

プロバイダーとBLoCでFlutter状態管理を簡素化

https://medium.com/fluttervn/simplify-flutter-state-management-with-provider-and-bloc-dcfad49bedf2


ラムはプロバイダーライブラリをテストしており、いくつかの問題を見つけた後、プロバイダーとBLoCの組み合わせがどのようにうまく機能するかを説明します。

Flutterネットワークリクエストをデバッグする3つの簡単な方法

https://medium.com/flutter-community/three-easy-ways-to-debug-network-requests-in-flutter-53043e898929


Flutter作業するときにネットワーク要求をデバッグするのに役立ついくつかのヒント。

Flutterレスポンシブデザイン:はじめに

https://www.raywenderlich.com/4324124-responsive-design-for-flutter-getting-started


JB Lorenzoは、 Flutterさまざまな画面サイズと向きを処理する方法に関する洞察を提供します。

ビデオ&メディア

Flutter TDDクリーンアーキテクチャコース

https://www.youtube.com/watch?v=KjE2IDphA_U&feature=youtu.be&t=2s


コードをクリーンに保ち、テストすることは、2つの最も重要な開発プラクティスです。 Clean ArchitectureをFlutterプロジェクトに適用する方法を学びます。

Flutter UI | Stadiaアプリのコンセプト

https://www.youtube.com/watch?v=-rqvZfUdSPw&feature=youtu.be&t=6395s


このビデオでは、CustomPainter、カスタムウィジェット、アニメーション、その他を使用して、レスポンシブで適応性のあるレイアウトを作成する方法を説明します。

Flutterアニメーション切り替えボタン

https://www.youtube.com/watch?v=XI3LFEvagBY&feature=youtu.be&t=610s


アニメーション付きのカスタムスイッチボタンを作成する方法を学びます。

Flutter Desktopの使用開始

https://www.youtube.com/watch?v=V09e5nb95lo&feature=youtu.be&t=236s


このビデオでは、 Flutterを使用してデスクトップアプリケーションを作成するための最初の手順を実行する方法を示します。

AdmobをFlutterに追加する方法

https://www.youtube.com/watch?v=xSrNB6ge66Q&feature=youtu.be&t=205s


firebase_admob dartパッケージを使用してフラッターにadmobを追加する方法に関するビデオチュートリアルのコーディング

セマンティクス(今週のFlutterウィジェット)

https://www.youtube.com/watch?v=NvtMt_DtFrQ&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=48


Semanticsを使用して、アプリのUIにウィジェットに関する情報を注釈するウィジェットを使用して、 Flutterアプリをより包括的にします。

ライブラリ&コード

henriquearthur / flutter_native_splash

https://github.com/henriquearthur/flutter_native_splash

AndroidおよびiOSでスプラッシュスクリーンを追加するためのネイティブコードを自動的に生成します。特定のプラットフォーム、背景色、スプラッシュ画像でカスタマイズします。

ketanchoyal / extended_navbar_scaffold

https://github.com/ketanchoyal/extended_navbar_scaffold

拡張可能なフローティングナビゲーションバーを備えたカスタム拡張足場

simformsolutions / flutter_showcaseview

https://github.com/simformsolutions/flutter_showcaseview

iOSおよびAndroidで機能を紹介できるFlutterプラグイン。

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