Firebase connection detection does not work after 60 seconds - android

As explained in some answers:
On Android, Firebase automatically manages connection state to reduce bandwidth and battery usage. When a client has no active listeners, no pending write or onDisconnect operations, and is not explicitly disconnected by the goOffline method, Firebase closes the connection after 60 seconds of inactivity.
The problem is that after 60s, even after I go to an activity with a complete new reference, event listener, etc.. It still says it is disconnect, when in fact, it is not.
val connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected")
var connectListener : ValueEventListener? = null
fun checkConnection() {
connectListener = connectedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val connected = snapshot.getValue(Boolean::class.java)!!
if (connected) {
Log.d("FRAG", "CONNECTED")
else{
Log.d("FRAG", "DISCONNECTED")
}
}
override
fun onCancelled(error: DatabaseError) {
System.err.println("Listener was cancelled")
}
})
}
override fun onDetach() {
super.onDetach()
if (connectListener != null){
connectedRef.removeEventListener(connectListener)
}
}
How can I make sure I maintain or create a new connection to Firebase? I call the checkConnection method every onAttach of a fragment and onStart of an activity.

If you have an active listener on any data that is read from the server, the connection should remain open unless you've explicitly called goOffline() in your code. Note that .info/connected itself does not require reading from the server, so does not keep the connection open.
It seems you're using the realtime database to build an presence system on an otherwise Firestore based app. In that case: Cloud Firestore uses a gRPC-based protocol to talk between client and server, while the Firebase Realtime Database uses web sockets. They're in no way compatible or even comparable. Keeping an active listener on data in Firestore does not keep a connection to RTDB open. That's why the example in the Firestore documentation also writes an actual data node to the realtime database.

Stream<Event> checkInternetConectivity() {
Stream<Event> connectivityCheck = _firebaseDatabase.reference().child('.info/connected').onValue;
Stream<Event> randomCheck = _firebaseDatabase.reference().child('connected').onValue;
return Rx.combineLatest2(connectivityCheck, randomCheck,(connectivityCheck, _) => connectivityCheck as Event);}
}
Firebase automatically disconnects from the realtime database in android after 60 seconds if there are no active listeners and listening to '.info/connected' isn't enough to keep the connection active. Creating another stream to listen to a random node in realtime database as a way around to this automatic disconnection.
This is my workaround to this problem in Dart/Flutter

Related

onStopped() is not invoked for workmanager

I build an app that is able to deploy a periodic work using workmanager and I want to perform some actions like saving key-value in sharePreference or uploading data to firebase when the work is told to be stopped either by users themselves or somehow the system, through overriding the onStopped method. But it doesn't work. I also tested it by setting a button to manually cancel the work using cancelAllWorkByTag to check how it works and nothing is working. Can someone help me out?
override fun onStopped() {
super.onStopped()
val sharedPreferences = applicationContext.getSharedPreferences("AppSharedPreference", Context.MODE_PRIVATE)
with(sharedPreferences.edit()) {
putString("working_state", "STOPPED")
commit()
}
val db = Firebase.firestore
val now = DateTime.now()
val input = hashMapOf("timestamp" to now.toString(), "stopped" to "the service is somehow stopped")
db.collection("test").document(now.toString()).set(input, SetOptions.merge())
}

Getting duplicate BLE notifications on Android

I'm developing a frame exchange sequence between an nRF52840 and an Android smartphone using the BLE protocol.
The first time I connect, everything works fine.
I activate the listening of BLE notifications by the Android smartphone with this method:
fun enableBleNotificationsOnCentral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val notificationConfiguration = service.getCharacteristic(characteristicUUID)
val result = currentBluetoothGatt.setCharacteristicNotification(notificationConfiguration, true)
println(result)
}
}
And I enable sending BLE notifications on the nRF52840 with this method:
fun enableBleNotificationsOnPeripheral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val descriptorConfiguration = service.getCharacteristic(characteristicUUID).getDescriptor(
descriptorUUID).apply {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
val result = currentBluetoothGatt.writeDescriptor(descriptorConfiguration)
println(result)
}
}
These methods are called each time my smartphone is connected to the nRF52840.
But if I disconnect and connect a second time, I receive each of the notifications in duplicate.
In addition, if I disconnect and connect a 3rd time, I receive each notification 3 times, and one more each time I reconnect.
I checked my code on the nRF52840 and it does not duplicate notifications.
Here is the method I call when I request a disconnection:
private fun disconnectFromCurrentDevice() {
currentBluetoothGatt?.disconnect()
BLECallbackManager.currentDevice = null
setUiMode(false)
}
I guess my problem is related to the fact that I don't disable the receipt of BLE notifications by my Android application when I disconnect but I'm not sure. And if that's where the problem comes from, when should I do it in the disconnect method? Can you help me?
I guess you're creating a new BluetoothGatt object for every new connection attempt, but you not destroy the previous one.
Try change disconnect() to close().

Remote config value fetching is inconsistent

I am fetching Firebase remote config values both in onStart() and in onStop(). The variable I am holding in "min_version", used for in-app updates feature. So my problem is that when I am going to remote config and updating the values, they are not updated immediately but save their old values for one more app lifecycle iteration before being updated to the new value. That means that if I make the decision that I want all users from a certain version to updated, they will not get the in-app update immediately, but only after one iteration through onStop and than get it.
here are my codes for onStart() and onStop() -
#Override
protected void onStart() {
/**
* We are fetching the minimum version that we want the user to have from Firebase Remote Config, only after we have the results we can proceed to start the app.
*/
getMinAppVersion("onStart", () -> {
// navigation drawer
checkValidFacebookSession();
initDrawerMenu();
// network monitoring
registerNetworkReceiver();
// monitoring upload
LocalBroadcastManager.getInstance(this).registerReceiver(mUploadReceiver, new IntentFilter(ULBroadcastConstants.UPLOAD_STATUS_ACTION));
LocalBroadcastManager.getInstance(this).registerReceiver(mFCMReceiver, new IntentFilter(MyFirebaseMessagingService.RECEIVED_FCM_ACTION));
checkInAppUpdate();
});
super.onStart();
}
#Override
protected void onStop() {
getMinAppVersion("onStop", () -> {
mNavigationView.setNavigationItemSelectedListener(null);
mDrawerLayout.removeDrawerListener(mBadgeDrawerToggle);
// network monitor
unregisterNetworkReceiver();
// unregister upload
LocalBroadcastManager.getInstance(this).unregisterReceiver(mUploadReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mFCMReceiver);
});
super.onStop();
}
here is my 'getMinAppVersion()' method -
private void getMinAppVersion(String didComeFrom, OnRemoteConfigFetchComplete listener){
//fetching the min_version parameter from 'remote config' of Firebase and saves it to our local variable.
FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder().setMinimumFetchIntervalInSeconds(200).build();
mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);
mFirebaseRemoteConfig.fetch(0);
mFirebaseRemoteConfig.activate().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
min_version = mFirebaseRemoteConfig.getLong(RemoteConfigUtil.MIN_VERSION);
Timber.tag("min_version_" + didComeFrom).d(String.valueOf(min_version));
if (listener != null)
listener.onFetchComplete();
} else {
Timber.tag("min version").d("error while fetching and activating remove config");
}
});
}
Here is why it's happening. The Remote Config caches the value in a local storage according to official docs. You can refer here and consider
"Remote Config includes a client library that handles important tasks like fetching parameter values and caching them, while still giving you control over when new values are activated so that they affect your app's user experience. This lets you safeguard your app experience by controlling the timing of any changes."
When you use the client library to fetch remote-config parameter, The cached value will be returned to you if it's there(TL;DL) for more information, you can read the official docs here. It uses minimum time interval to fetch the value to avoid app crashes, For that minimum interval time, last fetched value served as a cache
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(3600)
.build();
mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);
Here 3600 is time of minimum interval between new fetch, You can adjust the time according to your requirement. If you have recently called fetch() and once again you are calling it, client-library determines according to minimum-interval time that the new API call should be carried out or serve a cached value. Firebase remote config works on REST api and uses Throttling as well.
Remote Config updates in the console are not instant. They don't work like push notifications. The new values only take effect after the Task returned by fetch() completes, and you call activate() after that. I will point out that you're not using the Task returned by fetch() to find out when the new values have been received from Firebase.

Websocket connection wasn't closed after the app killed

My Websocket client (OkHttp) doesn't close the connection after the app closed. It opens a new connection every time I open the app which makes the app suffering from multiple messages received on the old and the new Websocket connections from the broadcasting server.
Is that a normal behavior for the android client, as for what I have experienced with web-client, the session was closed properly after the tab killed?
I have been looking up the problem across the internet but no luck so far. I want to make sure whether it happened because of my bad code logic or just the buggy Websocket's implementation from the library?
Here is how I start a new websocket session in the main Activity
var request: Request = Request.Builder()
.url("ws://$serverIP:8080/example/sim/speed")
.build()
var webSocketListener: WebSocketListener = object : WebSocketListener() {
override fun callback(msg: Message) {
updateSpeed(msg.content)
}
override fun onClosing(webSocket: WebSocket?, code: Int, reason: String?) {
super.onClosing(webSocket, code, reason)
}
}
var webSocket = client!!.newWebSocket(request, webSocketListener)
After that updateSpeed() will update a text view on UIThread
The onClosed event was not triggered when the app closed but only when the close function called manually.
I'm sure that it opened a new socket every time because I can see new sessions created on the server with different ports.
What I want is to have the app closing its connection before it was closed.
Thank you
I also encountered this issue, that WebSocket was still opened after my app was killed.
To solve this issue I manually close socket and remove all idle connections:
fun clear() {
webSocket?.close(1001, "Android: User exited the game.")
webSocket = null
subs.clear()
subs.add(Completable.fromAction {
client.connectionPool.evictAll()
}.subscribeOn(Schedulers.io()).subscribe({}, {}))
}
And I basically call clear() inside activity/ies, that might be opened during app kill.
override fun onDestroy() {
super.onDestroy()
App.webSocketClient().clear()
}
Not an ideal solution, but works every time.

firebase info/connected onDataChange 1 minute false no response

I have the following code called in onCreate to keep track of whether the user is connected to firebase/can connect to firebase:
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
connected = snapshot.getValue(Boolean.class);
}
#Override
public void onCancelled(DatabaseError error) {
connected = false; // not called in the observations below
}
});
Observed behavior:
A. When initialized:
onDataChange is called with connected set to false
onDataChange is called with connected set to true quickly thereafter (assuming had internet connection)
B. Before the 1 minute:
Turning off wifi or data will cause connected set to false
Turning on wifi or data will cause connected set to true
C. At 1 minute
connected set to false via onDataChange
D. After 1 minute
onDataChange is not called regardless of going on wifi/data
Expected behavior:
A'. Same as A
B'. Same as B
C'. Nothing
D'. Same as B.
I've seen other questions regarding losing authentication, but I haven't seen auth issues in my logs. Additionally the precise time of one minute seems very specific and was not seen in other similar question/answers.
Note that I'm using firebase 9.0.2.
Based on what I have observed on 9.0.2, the connection is closed after one minute when it is not needed. To leave the connection open would needlessly drain the battery.
A connection is needed when:
DatbaseReference.setValue() is executed.
Query.addListenerForSingleValueEvent() is executed
A ValueEventListener or ChildEventListener has been added and not removed
After a connection has been closed, one of the listed events will cause it to reopen. For events 1 and 2, the connection will close again after one minute. When a listener has been added (not for single value), the connection remains open until the listener is removed.

Categories

Resources