20201024のAndroidに関する記事は6件です。

#16 Kotlin Koans Conventions/For loop 解説

1 はじめに

Kotlin公式リファレンスのKotlin Koans/For loopの解説記事です。

Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。

ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!

一度各自で挑戦してから、お目通し頂ければと思います:fist:

2-1 for構文

for loop
forを用いることで繰り返し処理を記述することができます。

forを用いた構文にはいくつか種類があります。

(例1)
for(i in 1..10){
  print(i)
}

実行結果は、12345678910

---

(例2)
val collecion = listOf(1, 2, 3)
for (i in collection) {
  print(i)
}

実行結果は、123

(例1)の構文は

for(変数名 in 変数を繰り返す範囲){
   //繰り返したい処理
}

(例2)の構文は

for(変数名 in collection){
   //繰り返したい処理
}

collecionと書いた部分は、正確には

  1. iterator()を持っている。
  2. iterator()の戻り値の型のクラスがnext()関数を持っている。
  3. iterator()の戻り値の型のクラスがhasNext()関数を持っている。

という3つの条件をもったインスタンスのことです。

Collecionクラスを見るとIterableクラスを継承しています。Iterableクラスにはiterator()関数があり、この関数はIteratorクラス(next()とhasNext()を持つ)のインスタンスを生成します。)

本問では、(例2)の構文を使用します。

2-2 列挙型

enumという修飾子を用いて、{}で囲んだ中に定数(値)を宣言します。
そうすることで定義した定数(値)だけしか入れることのできない型を定義することができます。
これを列挙型と言います。

本章の内容を例に見てみましょう。

enum class TimeInterval{
   DAY,
   WEEK,
   YEAR
}

この場合、TimeIntervalクラスには3種の定数DAY/WEEK/YEARしか定義できないクラスを生成しています。

2-3 Calendarクラス

Calendarクラスについて

日付にまつわる関数やプロパティを持ったクラスと思えば大丈夫です。

getInstance():Calendarクラスのインスタンスを生成する。

set():プロパティYEAR, MONTH, DAY_OF_MONTHに値をセットする。

add():第1引数のCalendarクラスのプロパティに第2引数を足し合わせる。

get():指定したCalendarクラスのプロパティの値を得る。

また、本章で出てくるプロパティの

DAY_OF_MONTHDATEは同じものを指しています。

3 Conventions/For loopの解説

Kotlin Koans Conventions/For loopの解説です。
随時本サイトの内容を引用させていただきます。

右側の本文を見てみましょう。

Kotlin for loop iterates through anything that provides an iterator. Make the class DateRange implement Iterable, so that it could be iterated over. You can use the function MyDate.nextDay() defined in DateUtil.kt

左側のコードとMyDate.ktファイルとDateUtil.ktファイルのコードも見てみましょう。

For_loop
class DateRange(val start: MyDate, val end: MyDate)

fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
    for (date in firstDate..secondDate) {
        handler(date)
    }
}
MyDate.kt
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
    override fun compareTo(other:MyDate) = when{
        year != other.year -> year - other.year
        month != other.month -> month - other.month
        else -> dayOfMonth - other.dayOfMonth
    }
}

operator fun MyDate.rangeTo(other:MyDate) = DateRange(this,other)
DateUtil.kt
import java.util.Calendar

fun Mydate.nextDay() = addTimeIntervals(TimeInterval.DAY,1)

enum class TimeInterval{
   DAY,
   WEEK,
   YEAR
}

fun MyDate.addTimeIntervals(timeInterval: TimeInterval,number: Int):MyDate{
    val c = Calendar.getInstance()
    c.set(year,month,dayOfMonth)
    when(timeInterval){
        TimeInterval.DAY -> c.add(Calendar.DAY_OF_MONTH,number)
        TimeInterval.WEEK -> c.add(Calendar.WEEK_OF_MONTH,number)
        TimeInterval.YEAR -> c.add(Calendar.YEAR,number)
    }
    return MyDate(c.get(Calendar.YEAR),c.get(Calendar.MONTH),c.get(Calendar.DATE))  
}

MyDate.ktファイル

MyDateクラスに関しては、#13 Kotlin Koans Conventions/Comparison 解説を御覧ください。

MyDate.rangeTo()関数はMyDate型の引数を受け取り、DateRange型のインスタンス(DateRangeクラスのプロパティstartにthis(rangeTo()の呼び出しもとのインスタンス)/endにMyDate型の引数を代入している)を返します

つまり、rangeTo()を呼び出すと、DateRange型のインスタンスが返ってくるということですね。


DateUtil.ktファイル

nextDay()は、(後述の)addTimeIntervals関数に(後述の)TimeInterval.DAYと1を引数として渡した結果得られたMyDate型のインスタンスを戻り値として返します。

TimeIntervalクラスはenum修飾子がついており、クラス内で定数DAY/WEEK/YEARが定義されています。

addTimeIntervals関数はTimeInterval型とInt型の引数を受け取りMyDate型のインスタンスをかえします。

変数cにはCalendar.getInstance()によってCalendarクラスのインスタンスが代入されます。

c.set()では、CalendarインスタンスのYEAR/MONTH/DAY_OF_MONTHプロパティに対して、addTimeIntervals()を呼び出したMyDateインスタンスのyear/month/dayOfMonthプロパティを代入しています。

 when(timeInterval){
        TimeInterval.DAY -> c.add(Calendar.DAY_OF_MONTH,number)
        TimeInterval.WEEK -> c.add(Calendar.WEEK_OF_MONTH,number)
        TimeInterval.YEAR -> c.add(Calendar.YEAR,number)
    }

の部分ではtimeIntervalがTimeInterval.DAY/TimeInterval.WEEK/TimeInterval.YEARのいずれかによって処理が分岐しており、
add()を用いてCalendar型インスタンスのプロパティDAY_OF_MONTH/WEEK_OF_MONTH/YEARのいずれかに対してnumberを足し合わせています。

まとめると、
nextDay()関数を呼び出したら、呼び出し元のMyDateに1日後の日付が返る
ということを理解して貰えればOKです。


それでは、コードの実装について考えてみましょう。

まずはfor構文についてです。

for (date in firstDate..secondDate) {
        handler(date)
}

firstDate .. secondDateの部分は上述した3つの条件が必要です。

firstDate .. secondDateはMyDate.ktファイルのrangeTo()を呼び出します。

operator fun MyDate.rangeTo(other:MyDate) = DateRange(this,other)

rangeTo()は戻り値としてDateRangeクラスのインスタンスを返します。

つまり、for構文に必要な3つの条件を満たすには

DateRangeクラスがIterableクラス(iterator()を持つ)を継承する必要があります。

なので、以下のように実装します。

For_loop
class DateRange(val start: MyDate, val end: MyDate):Iterable<MyDate>{
}

(<>内の型がMyDateとなっているのは、MyDate型のインスタンスがrangeTo()を呼び出したからです。)

ただし、これでは条件の1.しか満たしていません。

2と3の条件を満たすには、iterator()関数をオーバーライドして戻り値の型(Iterator< MyDate >)が、
next()hasNext()を持つ必要があります。

なので、以下のようにDateIteratorという独自のクラスのインスタンスを戻り値として設定します。

For_loop
class DateRange(val start: MyDate, val end: MyDate):Iterable<MyDate>{
    override fun iterator():Iterator<MyDate> = DateIterator(this)
}

DateIteratorクラスにIterator< MyDate >クラスを継承させ、クラス内にnext()とhasNext()を実装していきます。

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
}

dateRangeのもつ、MyDate型のプロパティstartendを利用します。

基準となる日付をcurrentとしてstartを代入します。

また、hasNext()はfor(i in collection)のcollectionの部分が次の要素を持っているかを判定します。

したがって以下のように実装します。

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
    var current: MyDate = dateRange.start
    override fun hasNext():Boolean = current <= dateRange.end
}

next()はcurrentを返し、かつ、currentの次の日付を生成します(既存のnextDay()を利用)。

なので、以下のように実装します。

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
    var current: MyDate = dateRange.start
    override fun next(): MyDate{
        val result = current
        current = current.nextDay()
        return result
    }
    override fun hasNext():Boolean = current <= dateRange.end
}

4 最後に

次回はKotlin Koans Conventions/Operators overloadingの解説をします:muscle:

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

Ionic+Angular 環境構築からアプリビルドまで

環境

  • macOS v10.15.5
  • npm v6.13.4
  • Ionic v6.10.1
  • Xcode v12.0.1
  • Android Studio v4.0.1

Ionic環境構築

Nodeを以下からインストールします
https://nodejs.org/ja/

Ionic CLIをインストールします

% sudo npm install ionic -g

Ionic+Angularプロジェクトを作成

以下コマンドでIonic+Angularプロジェクトを作成します

% ionic start --type=angular

以下のように聞かれるので? Project name:の後に任意のプロジェクト名を入力します

Every great app needs a name! ?

Please enter the full name of your app. You can change this at any time. To
bypass this prompt next time, supply name, the first argument to ionic start.

? Project name:

プロジェクト名設定後ionicのテンプレートを選択します
モバイルアプリでよく使うUIが用意されているようです(本記事ではtabsを選択)

? Starter template: (Use arrow keys)
❯ tabs         | A starting project with a simple tabbed interface
  sidemenu     | A starting project with a side menu with navigation in the cont
ent area
  blank        | A blank starter project
  list         | A starting project with a list
  my-first-app | An example application that builds a camera with gallery
  conference   | A kitchen-sink application that shows off all Ionic has to offer
(Move up and down to reveal more choices)

以下のように聞かれるのでyを入力して次に進みます

yを選択するとCapacitorというライブラリによってプロジェクト内にiOSとAndroidが追加されます

? Integrate your new app with Capacitor to target native iOS and Android? (y/N)

Nを選択して進んだ場合でも以下コマンドで後から追加することが可能です

% ionic integrations enable capacitor
% npx cap add ios
% npx cap add android

以下が表示されていればプロジェクト作成完了です

Your Ionic app is ready! Follow these next steps:

ブラウザで立ち上げる

以下コマンドでブラウザでプレビューを表示します

% ionic serve

以下のように表示されました
プロジェクト作成時点でtabsテンプレートを選択したのでタブが自動生成されています
Screen Shot 2020-10-24 at 16.45.39.png

アプリとしてビルドする

コマンドにてXcodeかAndroidを起動するだけでビルド自体は各IDEの機能で行います
プロジェクト作成時点で前述したCapacitorライブラリが利用可能になっていることが前提です。

iOSの場合
以下コマンドでXcodeが自動で起動します

% npx cap open ios

Androidの場合
以下コマンドでAndroid Studioが自動で起動します

% npx cap open android

また以下コマンドで各OS端末のプレビューをブラウザでも確認することができるようです。

% ionic serve --lab

Screen Shot 2020-10-24 at 16.58.43.png

この時点ですでにiOS、Androidらしいデザインで生成されていて素晴らしいです

おわり

次回はプロジェクトファイルの構成を調べて投稿予定です。(2020/10/25時点)

参考

https://ionicframework.com/jp/docs/angular/your-first-app
https://capacitorjs.jp/docs/getting-started/with-ionic

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

AndroidにおけるJSR-310実装

ちょっとツイッターに流れてきた情報を見て気になったので改めて調べてみました。

[https://twitter.com/red_fat_daruma/status/1268177028915716102:embed]

TL;DL

  • ThreeTenABPでJSR-310を利用する場合は環境によらず同じtz databaseを参照する(はず
  • desugaringでJSR-310を利用する場合は実行環境により異なるtz databaseを参照する(はず

java.util.Date と JSR-310

java.util.Dateが使い勝手が悪いことは良く知られており、Java SE 8ではjava.util.Dateを置き換えるDate and Time APIが追加された。このAPIはJSR-310として標準化されており、インターフェースがデベロッパーフレンドリーであるだけでなくインスタンスがイミュータブル、スレッドセーフといった機能的な優位性を持つ。[1]

AndroidでのJSR-310実装方法の変遷

前述の通りJSR-310はJava SE 8からの機能である。Android Gradle plugin4.0.0以降でJSR-310のdesugaring対応がされたが[2]、それまではThreeTenBPやThreeTenABP[3]のようなJava6,7向けにJSR-310をバックポートしたライブラリを利用する必要があった。またThreeTenBPはJSR-310と完全に同じ振る舞いをするわけではないという点にも注意が必要でタイムゾーン(tz database)は独自に実装されておりJREではなくThreeTenBP自体にパッケージされたtz databaseを参照するため、tz databaseの更新にはThreeTenBPの更新が必要であった。[4][5]

tz databaseについて

世界各地域の標準時(time zone、タイムゾーン)情報をまとめたデータ群のことである、以下のように標準時からの差をタイムゾーンとオフセットにより定義する。厄介なのが世界各国でこの定義の更新は割と頻繁に行われているようで、年に数回はtz databaseの更新が行われていることだ。[6]

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Asia/Tokyo      9:18:59 -       LMT     1887 Dec 31 15:00u
                        9:00    -       JST     1896
                        9:00    -       CJT     1938
                        9:00    Japan   J%sT

ZonedDateTime: 2020-08-01 00:00:00 (Asia/Tokyo)
OffsetDateTime: 2020-08-01T00:00:00+09:00

Androidにおけるtz database

Android Gradle plugin4.0.0以降でJSR-310のdesugaring対応がされたと述べたが、これまでThreeTenABPで利用していたJSR-310に見えるものとdesugaringで利用可能となるJSR-310はtz databaseの取り扱いなど細かい部分で実は挙動が異なる。ThreeTenABPが参照するtz databaseはパッケージングされたものであるのに対してdesugaringしたJSR-310が参照するのはJava実行環境のtz databaseとなる。Android10以降の端末でいうと/system/apex/com.google.android.tzdata.apex に含まれているtz databaseを参照する。

またAndroid10以降でAPEXベースのモジュール更新メカニズム[7]が採用されたようでtz databaseの更新方法が変わっているようだが、Android9以前のOEMがAPKベースのメカニズムでtz databaseを端末にプッシュしていた方法と比べて今回の件について本質的な差異はないように思う。

まとめ

JSR-310で実装するといってもThreeTenABPを使うかdesugaringを使うかで参照するtz databaseは異なる。今後はdesguaringでJSR-310を利用するほうに流れていくと思うが、その場合実行環境によって異なるバージョンのtz databaseを参照することは避けられないのではないだろうか。

参考

[1] [https://builder.japan.zdnet.com/sp_oracle/35067620/:title]

[2] [https://developer.android.com/studio/write/java8-support#library-desugaring:title]

[3] [https://github.com/JakeWharton/ThreeTenABP:title]

[4] [https://www.coppermine.jp/docs/notepad/2016/12/threeten-extra-and-threeten-backport.html:title]

[5] [https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/zone/TzdbZoneRulesProvider.html:title]

[6] [https://www.oracle.com/java/technologies/tzdata-versions.html:title]

[7] [https://source.android.com/devices/architecture/modular-system/timezone?hl=ja:title]

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

Error: Activity class [Activity名 ] does not exist.解決法

実機ビルドでエラー発生!

とにかく早くこのエラーを解決したい方の為にまず試して欲しいキーワードを書いておきますので、それをコピペしてググってみてください。簡単に試せる方法からご紹介します。

1.GoogolePlayStoreのPlayプロテクトを外す
2.Clean ProjectをしてAndroidStudioを再起動する
3.adb uninstall [パッケージ名] というコマンドを試す

以下は私のエラー遭遇振り返りです。

バグ修正を反映させるため、実機にあったアプリをアンインストールしていざデバッグ起動しようとしたら突然の拒絶。

Error: Activity class [Activity名 ] does not exist.

え…?Activityあるよ…?え…?(パニック)

色々と試しましたが最終的に解決に至った方法は…

GoogolePlayStoreのPlayプロテクトを外す

信じられないくらい簡単な解決法。

これでもう安心かと思いきや、また同じエラーに遭遇。

GoogolePlayStoreのPlayプロテクトは外されているのに何故だ…!と、思いましたが、最初にたくさん解決方法を検索して試していたおかげで思い当たることがありました。
それは、アンインストールしたアプリが実はまだ完全に消去できていないという可能性。
adbコマンドを使用してアプリを完全に消去してから再びインストールしてみると、ちゃんと実機ビルドに成功しました。

% adb uninstall [パッケージ名]

adbコマンドを使うためにはパスを通す必要がありますので、設定した記憶がない人はググってみてください。
(ご自身が使っているターミナルがbashかzshかは気をつけてくださいね。私がまだよく分かっていなかった時は、サイトのコマンドをそのままコピペして使っていたので、bash使っているのにzshにパス通してたりしてましたから…)

さて、adbコマンドとは…?という人のために少しだけ解説します。

adb(Android Debug Bridge)

android開発で実機(orエミュレータ)を使って動作を確認する時、PCから実機(orエミュレータ)を動かせるようにするコマンドラインツールです。つまり、PCとデバイスを繋いでくれる橋渡しの役割を担っているのがadbコマンドというわけですね。
ほとんど使わないような気もしますが、今回のように実機上のどこかにキャッシュが残っていたりしてどうにもいかない場合などには便利です。
先ほどのように、デバイス上のアプリをPCからadbコマンドを送ってアンインストールしたりすることができます。
詳しくはAndroidDevelopersにありますのでこちらをご覧ください。

おわりに

このエラーが発生する前にも何度かインストールとアンインストールをしていたのに、急に実機ビルドできなくなって驚きました。しかもGoogolePlayStoreのPlayプロテクトを外していない状態で操作できていたので、何故急にadbコマンドを使用しないとアンインストールできなくなったのかは謎です。
しかし、今回のことをきっかけにadbコマンドを知ることができたので勉強になりました。
この記事が誰かのお役に立てることを願います。

この記事の内容に関してミスがあった場合か、私の知識がアップデートされた場合には加筆・修正を行います。

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

CloudFirestoreの簡単な使い方(Android)

はじめに

CloudFirestoreのAndroidでの基本的な使い方をメモ。
(導入から簡単なデータの送信受信など。)
使用言語はJava、開発環境はAndroidStudioを使用しています。
初投稿なので見にくい点が多々あると思いますがご了承を。

Firestoreについて

FirestoreはNoSQLデータベースです。
ファイル構造は画像の通りです。
コレクション内にドキュメントが入っていてその中のデータを使います。
ドキュメント内にサブコレクションを作成することも可能です。
structure-data.png

プロジェクトの作成、準備

Firebaseのホームページからプロジェクトを作成をクリックし、プロジェクトの追加をクリック好きな名前を設定しアナリティクスは有効、アカウントを選びプロジェクトを作成。
アプリは今回はAndroidなのでAndroidを選択。

Android アプリに Firebase を追加

手順1 アプリの登録
ここでの入力はAndroidパッケージのみで大丈夫です。
必要な場合はニックネームやSHA-1を記入。(今回は入力なし)
手順2 設定ファイルのダウンロード
google-services.jsonをダウンロードし
左上の表示方法をProjectに切り替えappにjsonファイルを導入
サイトの右の画像と同じ場所にjsonファイルが存在しているならOK
手順3 Firebase SDKの追加
サイトに表記されている内容をビルドグレイドルに追加。
サイトに表記されている内容とは別に以下の内容をアプリレベルのグレイドルに追加。

app
dependencies {
implementation 'com.google.firebase:firebase-core:17.0.0'
implementation 'com.google.firebase:firebase-firestore:20.0.0'
}

バージョンはこのサイト下の方の使用可能なライブラリから最新のものを調べて表記。

手順4 アプリを実行してインストールを確認
ここは終わらない場合があるためスキップで大丈夫です。
確認はアプリを実行し、logcatでI/FirebaseInitProvider: FirebaseApp initialization successfulが表示されていれば完了しています。
手順5 firestoreの作成
サイト左側タブのCloud Firestoreをクリックしてデータベースの作成
ルールは今回は本番環境モードを選択。
(テストモードは30日更新しないとアクセス拒否になります)
ロケーションは適当な場所を選びます。
そのままだとルールが厳しいため
write: if falseをwrite: if trueに変更します
53c13ab8b30c6ebcc4556a9541a4f954.png
以上で準備は完了です。

コード

送信・受信と他使えそうなコードを少し記述します。
Firestoreを確認しながら実行してみてください。

ドキュメントとコレクションの指定

以下の文で指定することができます。
指定したドキュメント/コレクションが存在しない場合データを保存する時に作成されます。

DocumentReference mDocRef = FirebaseFirestore.getInstance().document("コレクション名/ドキュメント名");

送信

まず初めに送信の処理のコードを紹介します。

Mapの保存

Map<String, Object> data = new HashMap<>();
       data.put("A","りんご");
       data.put("B","みかん");
       data.put("C","ぶどう");
        mDocRef.set(data);

リストの保存

ArrayList<String> group = new ArrayList<>();
        group.add("りんご");
        group.add("みかん");
        group.add("ぶどう");
        Map<String, Object> data = new HashMap<>();
        data.put("フィールド名",group);
        mDocRef.set(data);

受信

次にFirebaseの値を取得するときのコードを紹介します

Mapの取得

mDocRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if (task.isSuccessful()) {
                    DocumentSnapshot document = task.getResult();
                    if (document.exists()) {
                       //値が取得できた時の処理
                        String save = (String) document.get("フィールド名");//
                    } else {
                        //値が存在しなかった時の処理
                } else {
                    //取得に失敗した時の処理
                }

            }
        });

リストの取得

mDocRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if (task.isSuccessful()) {
                    DocumentSnapshot document = task.getResult();
                    if (document.exists()) {
                       //値が取得できた時の処理
                       ArrayList<String> group =  (ArrayList<String>) document.get("フィールド名");
                    } else {
                       //値が存在しなかった時の処理
                    }
                } else {
                    //値の取得に失敗した時の処理
                }

            }
        });

他によく使いそうなコード

次に使いそうなコードを記述します。

保存が成功・失敗した時の処理

データを送信する処理の後に記述します。

mDocRef.set(data).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    /// 保存が成功した時の処理
                } else {
                    /// 保存が失敗した時の処理
                }
            }
        });

値が変更された時の処理

起動時と値が変更された時に実行されます

mDocRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
                    @Override
                    public void onEvent(@Nullable DocumentSnapshot snapshot,
                                        @Nullable FirebaseFirestoreException e) {

                        if (snapshot != null && snapshot.exists()) {
                            Log.d("TAG", "Current data: " + snapshot.getData());
                            //変更されたときの処理
                        }
                    }
        });

さいごに

以上でFirestoreの使い方の紹介を終わります。
ありがとうございました。

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

Firebase赤ちゃんがKotlinでPUSH通知を送信するまで

はじめまして。プロ赤ちゃんです。
この記事では、FirebaseのFも理解できていない赤ちゃんが三日三晩苦しんだ末にFirebase赤ちゃんからFirebase幼稚園に入園するまでのサクセスストーリーを書いていきます。
よろしくおねがいします

Firebase赤ちゃんの誕生(サイトアクセス~ログインまで)

Firebaseのサイトにアクセスし、押してくれと言わんばかりの「使ってみる」ボタンを押します。
Googleアカウントのログイン画面が出てくるのでログインするかアカウントを作るかして先に進みます。
そうしたら↓の画面が出てきました!Firebase赤ちゃんの誕生です。
image.png

次はプロジェクトを作成します。

Firebase乳児期(プロジェクト作成まで)

さっきの画面で出てきた、「プロジェクトを作成」ボタンを押してみます。
image.png
プロジェクトが何か分からないまま、いきなり名前をつけてと言われました。赤ちゃんはプロジェクトがなんなのか分からないので名前を付けるわけにもいかず泣いてしまいます。
image.png
どうやら、プロジェクトとは幼稚園のクラスのようなものらしいです。
同じクラスのお友達は同じ絵本やおもちゃが使えるように、同じパッケージの中にあるアプリは同じ機能が使えるみたいです。
これがあればAndroidとiOSでバラバラにアプリを作ったり、あとから作ったアプリにユーザー情報の引き継ぎが出来たりしそうですね!
赤ちゃんはAndroid専門なので、iOS専門の赤ちゃんがいれば一緒のプロジェクトを使って両OSの対応が出来そうです!

プロジェクトについて理解できたので、先に進みます。
image.png
今回はちゃんとしたプロジェクトではないので適当に付けました。
プロジェクト名は自分以外の赤ちゃんやお兄さんお姉さんが見て、何のアプリのプロジェクトか簡単に分かる名前がついているといいですね。

「続行」ボタンを押すと、↓の画面に進みました。
image.png
またわけわかんないことがいっぱい書いてあってぐずりそうです。
PUSH通知に必要な「Cloud Messaging」がこの中にいるのが見えました。ほかにも色々と大事そうな機能が見えるので、有効にして「続行」を押します。

image.png
ゴールが見えてきました!
アナリティクスの地域を選択します。最初は「アメリカ合衆国」が選択されているので、プロジェクトのターゲットとなる国を選択します。
赤ちゃんは「日本」を選びました。海外をターゲットにしているプロジェクトでない限り、自国を選択でいいと思います。
Googleアナリティクス関連の規約にすべて同意できたら、いよいよ「プロジェクトを作成」ボタンを押します。

image.png
待機画面でしばらく待っていたら、プロジェクトが準備できたみたいです!
早速「続行」ボタンを押してプロジェクトを確認しに行きましょう!

image.png
プロジェクト画面です!
image.png
Firebaseのトップ画面(コンソール)にも、しっかりプロジェクトが存在していますね!

次はいよいよアプリケーションを連携していきます

Firebase幼児期(Androidアプリケーションを追加する)

赤ちゃんは失敗を重ねて成長するものです。
つまり、Firebase赤ちゃんはアプリケーション連携で失敗しました。

大前提

連携するアプリを用意しておきます。
ない場合は適当に作っておいてください。起動できる状態なら何でも構わないので、新規作成した直後ので大丈夫です。
赤ちゃんは乳児期に作成したFirebaseプロジェクト名に合わせて「ExampleAndroidApp」というアプリケーションを用意しました。

Firebase連携失敗例その1

まずFirebaseのサイト側から進めました。
プロジェクト画面のドロイド君(Androidのアイコン)をクリックして、↓の画面に進みました。
image.png
Androidパッケージ名には用意したアプリのパッケージ名を設定します。Activityの一番上に書いてあるヤツです。com.から入れます。
アプリのニックネームはつけてもつけなくてもOKです。アプリ名が長かったり、複数登録する場合にすぐわかる状態にしておきたい人はつけておくのをオススメします。
赤ちゃんはこのプロジェクトにこれしか登録しないので省略しました。
最後にデバッグ用の署名証明書を要求されました。
赤ちゃんはこれが分かりませんでした。わからなかったのでスキップしました。
その結果連携が正しくできませんでした。

Firebase連携失敗例その2

赤ちゃんは前回の失敗から、署名証明書を取得しなかったことが原因だと思いました。
署名証明書を取得するにはkeytoolが必要らしいです。
赤ちゃんの環境ではkeytoolがなかったので、オラクルアカウントを作ってjavaをインストールし、AndroidStudioのターミナルからkeytoolの置かれたディレクトリに移動して

keytool -list -v \
-alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore

を実行します。パスワードを要求されますが、初期パスワードandroidで通ります。
参考:https://developers.google.com/android/guides/client-auth

これでいける!と赤ちゃんは信じ切っていましたが、これでも連携できませんでした。
ここでついに「Firebase側から連携できないのでは?」という考えに至りました。

赤ちゃんでもAndroidStudioからFirebaseにアプリを連携できるのか

結論としては、AndroidStudio側から簡単に設定できました。

AndroidStudioの上のほうにある「tools」から「Firebase」を選択します。
すると、画面右側にアシスタントタブが現れました。
image.png
※赤ちゃんは英語がわからないので日本語化プラグインを入れています
今回使うのは「Cloud Messaging」なので、タブを開いて、「Set up Firebase Cloud Messaging」を押します。

①アプリとFirebaseを連携

image.png
①Connect your app to Firebaseから、「Connect to Firebase」を押すと、ブラウザのFirebaseコンソール画面が立ち上がります。
image.png
アプリを連携したいプロジェクトを選んでね!と書いてあるので、先ほど作ったexampleProjectを選びます。
image.png
連携できました!さっきまでいっぱい泣いてたのは何だったんでしょうか。「接続」ボタンを押します。
image.png
真っ白い画面に英語でちょっとちびっちゃいました。多分AndroidStudioに戻ってね~みたいなことが書いてある気がするので、おむつを替えながらAndroidStudioに戻ります。ブラウザは閉じちゃってOK。
image.png
①に接続チェックが付いてる!成功です!

②FCMを追加

このままの勢いで②Add FCM to your appも追加しちゃいます。
image.png
こんなダイアログが出てきました。
プロジェクトのbuild.gradle、アプリのbuild.gradleにそれぞれ緑の部分が追加されるみたいですね。
手動でコピペして…ってやる手間が省けてらくちんです!「Accept Changes」を押して、Gradle同期が完了するのを待ちます。
image.png
無事同期が終われば、こっちにもチェックマークがつきました!完了です!

余談

赤ちゃんは①までの過程で疲れてしまい、②を翌日にしたためアシスタントをど忘れして手動で追加したためにFCMが正しく連携されないという罠に陥って泣いていました。
もし、

import com.google.firebase.messaging.FirebaseMessagingService

でmessagingが赤くなってFirebaseMessagingService().onNewToken(String)がoverrideできないよ~!という赤ちゃんがいましたら、一度「tools→Firebase→Cloud Messaging→Add FCM to your app」の項目を確認してから再度書き直したりリビルドしたりすると解決するかもしれないです。(今回の記事ではあまり関係のない部分なので、無関係赤ちゃんはこれがimport出来なかったらここまでのどこかで失敗している可能性があるんだな~くらいに思っておいてください)

Firebase幼児後期(Kotlinで通知を受信して表示させる)

やっと通知に会えます!非常に長い道のりでした。

ちなみに幼児期前期で飛ばしたFirebaseアシスタントの③Access the device registration token以降の項目ですが、もしここも対応してから進みたいんだ!という堅物赤ちゃんがいましたら、ここは非推奨となったFirebaseInstanceIdServiceのトークンを見ているので注意してください。
②までで通知の受信は出来るので今回は対応せずに進めています。
赤ちゃんの環境はAndroidStudio3.6.3なのですが、これって4.xとか新しいバージョンなら更新入ってるんですかね?案件都合とかもあって4系にまだアップデートしてないのでわからないのですが、今までやさしかったAndroidStudioくんに突き放されて大泣きです。ぴえん

通知の土台作り

実装内容に関してはこちらのサイトを参考にしました。

AndroidManifest.xml
<!-- applicationタグの中に追加 -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/channel_id"/>

<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_launcher_foreground" />

<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/background" />

通知チャンネル、タスクバーに表示される通知アイコン、通知バーの背景色を定義します。
怠惰赤ちゃんなので通知アイコンは元から入っているランチャーアイコンにしました。

strings.xml
<string name="channel_id">channelId</string>
<string name="channel_name">通知チャンネル名</string>

通知チャンネル名はまだ使いませんが、チャンネルIDと一緒に設定しておきます。

colors.xml
<color name="white">#FFFFFF</color>
<color name="background">@color/white</color>

これでバックグラウンド時のみですが通知を受け取る準備が整いました。

Firebase幼稚園 入学テスト

さて、ここまで来たらやっと送信テストです。
Firebaseコンソールの左端メニューから、「拡大」→「Cloud Messaging」を選択します。
image.png
image.png
「Send your first message」を押して最初の通知を送ってみましょう。
image.png
送信内容を適当に入力して「次へ」
image.png
ターゲットアプリに通知を送信したいアプリを選んで「次に」
スケジュールは「今すぐ」(初期項目)、コンバージョンイベントは空のままで大丈夫です。
image.png
最後に、その他のオプションで「Android通知チャンネル」に先ほど設定したchannel_idを設定します。
省略化ですが、Android8以降は通知チャンネルの設定が必須なので、これを設定しないと8以降の端末に通知が届かなくなります。
Androidを使っている大半のユーザーは既に8以上なので、余程古いアプリでもない限り実質必須項目ですね。
image.png
「確認」を押すとこんなダイアログが出てくるので、アプリがバックグラウンドになっていることを確認したら「公開」を押して通知を送信します!
image.png
Cloud Messagingのトップ画面に送信した通知が表示され、ステータスが完了になってるのを確認している間にスマホの通知が鳴りました!
タスクバーを開くと…
Screenshot_20201023-235140.png
通知が届いてます!(Androidのスクショすごいでかくなっちゃった…)

おめでとう!Firebase幼稚園入園です!

Firebase赤ちゃんを卒業した感想

今回、訳の分からない部分で詰まることが多く、調べても出てこない内容が多かったので非常に苦しかったです。
連携が終わって通知送信に成功できればあとはわりかしサクサク進みます。(進むとは言っていない)

調べても出てこないといえば何故かPush通知の記事って母数があんまりないらしいんですよね。
Push通知って結構カスタマイズの幅が効くものだし、大体のアプリに実装されているものなのに…

このままだと通知チャンネル名が未設定なので「その他」として表示されたり、アプリがフォアグラウンド時に通知が来なかったり、通知が1件しか溜らなかったり、なんやかんや使い勝手が悪いです。
なのでそれを改善していったり、AndroidのPush通知オプションについて色々と試した記事をFirebase幼稚園卒園論文として次回上げられたらいいな~と思っています!
あとは幼児期前半の最後に余談として挙げた部分の話とかも後続赤ちゃんのために残しておきたいですね(赤ちゃんなので次書く前にpushで苦しんだ記憶を忘れちゃったらごめんなさい)

Qiitaはじめての投稿な上に画像添付祭りになってしまったので、いろんな意味で読みにくい記事になってしまったかと思いますが、ここまで読んでくださりありがとうございました!

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