Firebase Summit で発表されたすべての情報をご覧ください。Firebase を使用してアプリ開発を加速し、自信を持ってアプリを実行する方法を紹介しています。詳細

Androidでオフライン機能を有効にする

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

アプリが一時的にネットワーク接続を失った場合でも、Firebase アプリケーションは動作します。さらに、Firebase は、データをローカルに永続化し、プレゼンスを管理し、レイテンシを処理するためのツールを提供します。

ディスクの永続性

Firebase アプリは、一時的なネットワークの中断を自動的に処理します。キャッシュされたデータはオフライン時に利用でき、Firebase はネットワーク接続が復元されたときに書き込みを再送信します。

ディスクの永続性を有効にすると、ユーザーまたはオペレーティング システムがアプリを再起動した場合でも、アプリはオフライン中に状態を維持できるように、データをデバイスにローカルに書き込みます。

わずか 1 行のコードでディスクの永続性を有効にできます。

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

持続性の振る舞い

永続性を有効にすると、オンライン中に Firebase Realtime Database クライアントが同期するデータはすべてディスクに保持され、ユーザーまたはオペレーティング システムがアプリを再起動した場合でも、オフラインで使用できます。これは、キャッシュに保存されたローカル データを使用して、アプリがオンラインの場合と同じように動作することを意味します。リスナー コールバックは、ローカル アップデートに対して引き続き発生します。

Firebase Realtime Database クライアントは、アプリがオフラインの間に実行されるすべての書き込み操作のキューを自動的に保持します。永続化を有効にすると、このキューもディスクに永続化されるため、ユーザーまたはオペレーティング システムがアプリを再起動したときに、すべての書き込みを利用できるようになります。アプリが接続を回復すると、すべての操作が Firebase Realtime Database サーバーに送信されます。

アプリでFirebase Authenticationを使用している場合、Firebase Realtime Database クライアントはアプリの再起動後もユーザーの認証トークンを保持します。アプリがオフラインのときに認証トークンの有効期限が切れると、アプリがユーザーを再認証するまで、クライアントは書き込み操作を一時停止します。そうしないと、セキュリティ ルールが原因で書き込み操作が失敗する可能性があります。

データを最新に保つ

Firebase Realtime Database は、アクティブなリスナーのデータのローカル コピーを同期して保存します。さらに、特定の場所を同期することができます。

Java

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);

Kotlin+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.keepSynced(true)

Firebase Realtime Database クライアントは、これらの場所にあるデータを自動的にダウンロードし、参照にアクティブなリスナーがない場合でも同期を維持します。次のコード行を使用して、同期をオフに戻すことができます。

Java

scoresRef.keepSynced(false);

Kotlin+KTX

scoresRef.keepSynced(false)

デフォルトでは、以前に同期された 10MB のデータがキャッシュされます。ほとんどのアプリケーションではこれで十分です。キャッシュが構成されたサイズを超えると、Firebase Realtime Database は最近使用されていないデータをパージします。同期が保たれているデータは、キャッシュから消去されません。

オフラインでのデータのクエリ

Firebase Realtime Database は、オフライン時に使用するために、クエリから返されたデータを保存します。オフライン中に作成されたクエリの場合、Firebase Realtime Database は以前に読み込まれたデータに対して引き続き機能します。リクエストされたデータが読み込まれていない場合、Firebase Realtime Database はローカル キャッシュからデータを読み込みます。ネットワーク接続が再び利用可能になると、データが読み込まれ、クエリが反映されます。

たとえば、次のコードは、Firebase Realtime Database のスコアの最後の 4 つの項目をクエリします。

Java

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

Kotlin+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

ユーザーが接続を失い、オフラインになり、アプリを再起動したとします。まだオフラインの間、アプリは同じ場所から最後の 2 つのアイテムを照会します。アプリは上記のクエリで 4 つの項目すべてを読み込んだため、このクエリは最後の 2 つの項目を正常に返します。

Java

scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

Kotlin+KTX

scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

前の例では、Firebase Realtime Database クライアントは、永続キャッシュを使用して、スコアが最も高い 2 つの恐竜の「子追加」イベントを発生させます。ただし、アプリはオンライン中にそのクエリを実行したことがないため、「値」イベントは発生しません。

アプリがオフライン中に最後の 6 つのアイテムを要求すると、キャッシュされた 4 つのアイテムの「子が追加されました」イベントがすぐに取得されます。デバイスがオンラインに戻ると、Firebase Realtime Database クライアントはサーバーと同期し、アプリの最後の 2 つの「child added」イベントと「value」イベントを取得します。

トランザクションのオフライン処理

アプリがオフラインのときに実行されるすべてのトランザクションは、キューに入れられます。アプリがネットワーク接続を回復すると、トランザクションが Realtime Database サーバーに送信されます。

プレゼンスの管理

リアルタイム アプリケーションでは、クライアントがいつ接続および切断するかを検出すると便利なことがよくあります。たとえば、クライアントが切断されたときにユーザーを「オフライン」としてマークしたい場合があります。

Firebase Database クライアントは、クライアントが Firebase Database サーバーから切断されたときにデータベースへの書き込みに使用できる単純なプリミティブを提供します。これらの更新は、クライアントが正常に切断されたかどうかにかかわらず発生するため、接続が切断されたり、クライアントがクラッシュしたりした場合でも、データをクリーンアップするために信頼できます。設定、更新、および削除を含むすべての書き込み操作は、切断時に実行できます。

onDisconnectプリミティブを使用して、切断時にデータを書き込む簡単な例を次に示します。

Java

DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");

Kotlin+KTX

val presenceRef = Firebase.database.getReference("disconnectmessage")
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!")

onDisconnect の仕組み

onDisconnect()操作を確立すると、その操作は Firebase Realtime Database サーバー上に存在します。サーバーはセキュリティをチェックして、要求された書き込みイベントをユーザーが実行できることを確認し、無効な場合はアプリに通知します。その後、サーバーは接続を監視します。いずれかの時点で接続がタイムアウトした場合、または Realtime Database クライアントによってアクティブに閉じられた場合、サーバーは (操作がまだ有効であることを確認するために) セキュリティを 2 回チェックしてから、イベントを呼び出します。

アプリは書き込み操作でコールバックを使用して、 onDisconnectが正しくアタッチされたことを確認できます。

Java

presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() {
    @Override
    public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) {
        if (error != null) {
            Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage());
        }
    }
});

Kotlin+KTX

presenceRef.onDisconnect().removeValue { error, reference ->
    error?.let {
        Log.d(TAG, "could not establish onDisconnect event: ${error.message}")
    }
}

onDisconnectイベントは、 .cancel()を呼び出してキャンセルすることもできます:

Java

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.setValue("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

Kotlin+KTX

val onDisconnectRef = presenceRef.onDisconnect()
onDisconnectRef.setValue("I disconnected")
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel()

接続状態の検出

多くのプレゼンス関連の機能では、アプリがオンラインかオフラインかを知ることができます。 Firebase Realtime Database は、Firebase Realtime Database クライアントの接続状態が変化するたびに更新される/.info/connectedという特別な場所を提供します。次に例を示します。

Java

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            Log.d(TAG, "connected");
        } else {
            Log.d(TAG, "not connected");
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

Kotlin+KTX

val connectedRef = Firebase.database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue(Boolean::class.java) ?: false
        if (connected) {
            Log.d(TAG, "connected")
        } else {
            Log.d(TAG, "not connected")
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

/.info/connectedはブール値であり、値はクライアントの状態に依存するため、Realtime Database クライアント間で同期されません。つまり、あるクライアントが/.info/connected false として読み取った場合、別のクライアントも false を読み取るという保証はありません。

Android では、Firebase が接続状態を自動的に管理して、帯域幅とバッテリーの使用量を削減します。クライアントにアクティブなリスナーがなく、保留中の書き込みまたはonDisconnect操作がなく、 goOfflineメソッドによって明示的に切断されていない場合、Firebase は非アクティブ状態が 60 秒間続いた後に接続を閉じます。

待ち時間の処理

サーバーのタイムスタンプ

Firebase Realtime Database サーバーは、サーバー上で生成されたタイムスタンプをデータとして挿入するメカニズムを提供します。この機能をonDisconnectと組み合わせると、Realtime Database クライアントが切断された時刻を確実に記録する簡単な方法が提供されます。

Java

DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

Kotlin+KTX

val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline")
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

クロック スキュー

firebase.database.ServerValue.TIMESTAMPははるかに正確であり、ほとんどの読み取り/書き込み操作に適していますが、Firebase Realtime Database のサーバーに対するクライアントのクロック スキューを推定するのに役立つ場合があります。 /.info/serverTimeOffsetの場所にコールバックをアタッチして、Firebase Realtime Database クライアントがサーバー時間を推定するためにローカルで報告された時間 (ミリ秒単位のエポック時間) に追加する値 (ミリ秒単位) を取得できます。このオフセットの精度はネットワーク レイテンシの影響を受ける可能性があるため、主にクロック時間の大きな (1 秒を超える) 不一致を検出するのに役立ちます。

Java

DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        double offset = snapshot.getValue(Double.class);
        double estimatedServerTimeMs = System.currentTimeMillis() + offset;
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

Kotlin+KTX

val offsetRef = Firebase.database.getReference(".info/serverTimeOffset")
offsetRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val offset = snapshot.getValue(Double::class.java) ?: 0.0
        val estimatedServerTimeMs = System.currentTimeMillis() + offset
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

サンプル プレゼンス アプリ

切断操作を接続状態の監視とサーバーのタイムスタンプと組み合わせることで、ユーザー プレゼンス システムを構築できます。このシステムでは、各ユーザーがデータベースの場所にデータを保存して、Realtime Database クライアントがオンラインかどうかを示します。クライアントは、オンラインになるとこの場所を true に設定し、切断するとタイムスタンプを設定します。このタイムスタンプは、特定のユーザーが最後にオンラインだった時間を示します。

両方のコマンドがサーバーに送信される前にクライアントのネットワーク接続が失われた場合に競合状態を回避するために、ユーザーがオンラインとしてマークされる前に、アプリは切断操作をキューに入れる必要があることに注意してください。

以下は、単純なユーザー プレゼンス システムです。

Java

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline");

final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            DatabaseReference con = myConnectionsRef.push();

            // When this device disconnects, remove it
            con.onDisconnect().removeValue();

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(Boolean.TRUE);
        }
    }

    @Override
    public void onCancelled(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

Kotlin+KTX

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
val database = Firebase.database
val myConnectionsRef = database.getReference("users/joe/connections")

// Stores the timestamp of my last disconnect (the last time I was seen online)
val lastOnlineRef = database.getReference("/users/joe/lastOnline")

val connectedRef = database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue<Boolean>() ?: false
        if (connected) {
            val con = myConnectionsRef.push()

            // When this device disconnects, remove it
            con.onDisconnect().removeValue()

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(java.lang.Boolean.TRUE)
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled at .info/connected")
    }
})