I am setting sharedPreferences value on my fragment (Kotlin) , then I would like to use this value on my FirebaseMessagingService (Java). When I set value and destroy app and open again, there isn't any problem on my fragment. I can see the set value. So I am sure that sharedPreferences value updated by my fragment. But when I try to use that value on FirebaseMessagingService, I am getting always default value.
Here how I am setting on kotlin class:
sharedPref = activity?.getSharedPreferences("com.xxx.xxx.xxx",Context.MODE_PRIVATE)!!
private fun sharedPrefWrite(boolean: Boolean){
with (sharedPref?.edit()) {
this!!.putBoolean("notf", boolean)
apply()
}
}
This working good.
And here is how I am getting this data on FirebaseMessagingService:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
preferences = getApplicationContext().getSharedPreferences("com.xxx.xxx.xxx",Context.MODE_PRIVATE);
if(preferences.getBoolean("notf",true))
sendNotification(remoteMessage.getNotification().getBody());
}
and always service sending notification.
And I didn't start this service on my activity, It is only under Application tag on Manifest.xml like that;
<service android:name="com.xxx.xxx.xxx.newsFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Any suggestion about why I couldn't get this value ? Thanks
EDIT:
I just did couple debug and when application on resume (on foreground), firebase service getting value correctly.. But If application on background (onPause) or if application destroyed, service can't fetch correct data from SharedPreferences.
EDIT 2
I removed onMessageReceived function from my FirebaseMessagingService, and re-install that app to my device, and when App on destroyed, I got the notification even there is no 'onMessageReceived ' ...
LAST EDIT
Solution below
I had a similar problem, with not everything from Android api working correctly in my FirebaseMessagingService.
I figured this can have something to do with limitations of running background services from Oreo and higher, and FCM messages being of exception to those
https://developer.android.com/about/versions/oreo/background#services
I guess you only got this problem on Android>=8, right?
So what I did now, is two pieces of code, for pre-Oreo and Oreo-and-newer respectively, running from my onMessageReceived
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
val body = remoteMessage.getNotification().getBody()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
scheduleJobService(body);
} else {
runIntentService(body);
}
}
then in runIntentService I just start an Intent service (I guess you know how do do that), but this can work only pre-Oreo, because of the mentioned limitations
on Android 8 and higher you would need to schedule a JobService or implement your own custom BroadcastReceiver...
I choose JobService, because it was easier for me, and I didn't mind waiting sometimes a bit for Android to schedule my Job, what I did is:
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun scheduleNotificationJobService(body: String) {
val serviceName = ComponentName(getPackageName(),
NotificationJobService::class.java.name)
val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val notificationInfoBundle = PersistableBundle()
notificationInfoBundle.putString(Constants.EXTRA_NOTIF_BODY, body)
val builder = JobInfo.Builder(JOB_ID, serviceName)
.setOverrideDeadline(0)
.setExtras(notificationInfoBundle)
val notificationInfo = builder.build()
scheduler.schedule(notificationInfo)
}
then your JobService would kook like this:
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class NotificationJobService : JobService() {
override fun onStopJob(params: JobParameters?): Boolean {}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onStartJob(params: JobParameters?): Boolean {
params?.extras?.let {
val body = it.getString(Constants.EXTRA_NOTIF_BODY)
val preferences = getApplicationContext().getSharedPreferences("com.xxx.xxx.xxx",Context.MODE_PRIVATE)
if(preferences.getBoolean("notf",true)){
sendNotification(body);
}
}
return false
}
}
remember do declare it in AndroidManifest.xml
<service android:name=".push.NotificationJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
let me know pls if that works for you too :)
cheers
I solved the problem finally..
There are two types of messages in FCM (Firebase Cloud Messaging):
Display Messages: These messages trigger the onMessageReceived() callback only when your app is in foreground
Data Messages: Theses messages trigger the onMessageReceived() callback even if your app is in foreground/background/killed
So I have to post Data message for my app. Normally it is done something like that;
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
BUT This no longer works (Error 401).. There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, securely store the JSON file containing the key. Iwas need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I an use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Related
I need to pass String token from FirebaseAppCheck to Kotlin Multiplatform.
I can get access to Kotlin class and assign values, but it always return null because of fact that Kotlin compiles first. Below is my swift function that should send token to KMM.
func sendAppCheckTokenToKMM() {
// Kotlin class
let factory = FirebaseAppCheckFactory()
AppCheck.appCheck().token(forcingRefresh: false) { token, error in
guard let token = token else {
print("Unable to retrieve App Check token.")
return
}
// Get the raw App Check token string.
let tokenString = token.token
factory.tokenId = tokenString
}
}
Next my kotlin FirebaseAppCheckFactory looks like this.
class FirebaseAppCheckFactory {
lateinit var tokenId: String
fun isTokenInitialised() = ::tokenId.isInitialized
}
And last I need to send this token using suspended function in another class.
// FirebaseAppCheckService.kt
override suspend fun fetchToken(): String? {
if (firebaseAppCheckFactory.isTokenInitialised()){
return firebaseAppCheckFactory.tokenId
}
return null
}
Every time functions returns null. For now I cannot change fetchTokenFunction.
Main class is expected/actual to be using on Android and iOS as well. Implementation on Android is done, but on iOS I have to do this on platform first to initialize FirebaseAppCheck and then send app check token.
Is there another/better way to to this?
In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.
Expected behavior
I want to have a callback to listen to every topic I subscribe to just once per message sent. I mean, I want to subscribe to a topic 1000 times, but when a message is received, I want to listen to it just one time.
IDK if there is something I am doing wrong (I guess).
Actual behavior
I am developing a home security camera app.
I have a list of cameras that I own.
For every camera on the list, I subscribe to a topic.
Every 30s, I update the screen, and again I subscribe to a topic for every camera. This means a TOPIC could be subscribed many times.
Every time I receive a message on a topic, the callback fires messages about how many times the same topi was subscribed.
To Reproduce
Steps
haven a topic camera/123
subscribe the topic N times with the below method called subscribeWith
Send a message over camera/123
You will receive the message N times because the N time you subscribed to the topic
Reproducer code
Just variables
private var mqtt: Mqtt5AsyncClient? = null
private var username: String? = null
private var password: String? = null
private val serverHost: String,
private val serverPort: Int = 1883
Build the MQTT
private fun build() {
if (mqtt != null) return
mqtt = Mqtt5Client.builder()
.identifier(identifier())
.serverHost(serverHost)
.serverPort(serverPort)
.automaticReconnect()
.applyAutomaticReconnect()
.addConnectedListener { Timber.d("On Connected") }
.addDisconnectedListener { onMQTTDisconnected(it) }
.buildAsync()
}
Connecting the MQTT
fun connect(username: String, password: String) {
build()
this.username = username
this.password = password
mqtt?.connectWith()
?.keepAlive(30)
?.sessionExpiryInterval(7200)
?.cleanStart(false)
?.simpleAuth()
?.username("abc")
?.password("123".toByteArray())
?.applySimpleAuth()
?.send()
}
And then, subscribing a topic
Every time I subscribe a topic I use these fun
fun subscribeWith(topic: String) {
mqtt?.subscribeWith()
?.topicFilter(topic)
?.qos(MqttQos.AT_MOST_ONCE)
?.callback { t -> onConsumingTopic(t) } <- I THINK THIS IS THE IMPORTANT THING
?.send()
?.whenComplete { ack, error -> onTopicConnected(ack, error, topic) }
}
As mentioned in the comments, the only solution at the moment is to keep a list of the subscribed topics outside the MQTT client library and check it before subscribing to new topics.
I found the correct answer.
There is no need to register a callback for every subscribe call nor using a global array handling the registered topics as this:
mqtt?.subscribeWith()
?.callback { t -> onConsumingTopic(t) } <- This is not needed
Instead you could register one "global" callback for all messages, for example:
client.publishes(MqttGlobalPublishFilter.SUBSCRIBED, publish -> { ... } );
and then you can subscribe without providing a callback.
client.subscribeWith().topicFilter("test").qos(MqttQos.AT_LEAST_ONCE).send();
Complete example:
Building the MQTT
mqtt = Mqtt5Client.builder()
.identifier(identifier())
.serverHost(serverHost)
.serverPort(serverPort)
.automaticReconnect()
.applyAutomaticReconnect()
.addConnectedListener { onMQTTConnected(it) }
.addDisconnectedListener { onMQTTDisconnected(it) }
.buildAsync()
mqtt?.publishes(MqttGlobalPublishFilter.SUBSCRIBED) { onConsumingTopic(it) }
Connecting the MQTT
mqtt?.connectWith()
?.keepAlive(30)
?.sessionExpiryInterval(7200)
?.cleanStart(false)
?.simpleAuth()
?.username(context.getString(R.string.mqtt_user))
?.password(context.getString(R.string.mqtt_pw).toByteArray())
?.applySimpleAuth()
?.send()
Subscribing the topic
mqtt?.subscribeWith()
?.topicFilter(topic)
?.qos(MqttQos.AT_LEAST_ONCE)
?.send()
?.whenComplete { ack, error -> onTopicSubscribed(ack, error, topic) }
I am trying to retrieve data from an api using retrofit. The request am going to use needs an access token to pass in the header. The problem is that the token expires after 10 min and the refresh token request needs an unexpired token to create a new one!
So what should i do to keep the token refreshed by it self before passing 10 min?
I already tried Interceptor but it can't work with this type of problem because i need a valid token to get a new one
You can use a Worker and set it to run every 30min or so and set it to save the renewed token in your SharedPreference
here's an example for the Worker
class UpdateTokenWorkManger(
val context: Context,
params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
LoginHandler.refreshTokenSilently()
// Indicate whether the work finished successfully with the Result
return Result.success()
}
companion object {
private const val TAG = "Token Refresh "
const val TOKEN_REFRESH_WORK_MANGER_ID = "automatic_renew_token_work_manger"
fun renewToken() {
val periodicRefreshRequest = PeriodicWorkRequest.Builder(
UpdateTokenWorkManger::class.java, // Your worker class
30, // repeating interval
TimeUnit.MINUTES
)
val periodicWorkRequest: PeriodicWorkRequest = periodicRefreshRequest
.build()
WorkManager.getInstance(App.getApplication()).enqueueUniquePeriodicWork(
TOKEN_REFRESH_WORK_MANGER_ID,
ExistingPeriodicWorkPolicy.REPLACE,
periodicWorkRequest
)
}
}
to use this component you will need these dependencies
implementation "androidx.work:work-runtime-ktx:2.4.0"
also note that LoginHandler is the class that should be responsible for handling your login, refresh and logout scenarios.
and don't forget to add this line to your first Activity after the login Activity, for example: if you login in SplashActivity and after succesful authentication you redirect to MainActivity, then this line should be in MainActivity's onCreate function
UpdateTokenWorkManger.renewToken()
My app only needs FCM notifications while the app is in the background. For this reason, I actually don't need a FirebaseMessagingService. I have only added it to my app to handle the onNewToken callback in case the token changes while the app is running:
#AndroidEntryPoint
class ChatFirebaseMessagingService : FirebaseMessagingService() {
#Inject
lateinit var userRepository: UserRepository
#Inject
lateinit var sessionManager: SessionManager
override fun onNewToken(token: String) {
sessionManager.currentChatUser?.let {
userRepository.persistCurrentFcmToken(it)
}
}
}
persistCurrentFcmToken lives in the repository:
I also call this method when a user logs in
fun persistCurrentFcmToken(user: ChatUser) {
applicationScope.launch {
val token = messaging.awaitToken() ?: return#launch
val tokenList = user.fcmTokens?.toMutableList() ?: mutableListOf()
if (!tokenList.contains(token)) {
tokenList.add(token)
val userUpdate = user.copy(fcmTokens = tokenList)
userCollection.document(user.uid!!).set(userUpdate)
}
}
}
The FirebaseMessagingService feels a bit pointless since I don't need foreground notifications. Is there a way to get rid of it or is the onNewToken callback inevitable?
If your app doesn't have a FirebaseMessagingService and the token is changed while the user is not using the app, your server will send the messages to the old token, and the device won't receive them.
Creating a FirebaseMessagingService actually ensures that your application code gets notified of token changes when the app is not actively being used. There is no way to get such a notification without a FirebaseMessagingService.