Android - display In-app message natively - android

I would like my app tз display a notification of the incoming messages, but only when the application is active, similar to how many social apps do that.
I.e., the user has my messaging app open and he gets a notification slide in from top, within this Android application.
To my understanding, this is something that is called “in app messages” in Firebase.
However, I wouldn’t like to have firebase as a dependency, as I am not using any part of it: the notifications will be triggered by an open network connection that my app made.
I also so wouldn’t want to involve push notifications as I need this functionality only when the app is active.
What would be the best way to achieve this goal?
Basically what I am asking is how to make my own notification “bubble” in UI that shows up inside my app, similar to how it is done in messaging/dating apps (see Badoo, for example). Mainly I am wondering if there are any implemendations available that I could use or do I have to draw this stuff myself (using Fragments?)

It's a very broad question. So in broad strokes: Use some real time communications technology, such as sockets/websockets to listen for incoming messages, and hook up into lifecycle to start listening when the app moves into foreground (and stop when it moves out) [assuming that is the meaning of app being active - otherwise if you include foreground state, just start listening and don't unlisten) -
class MyListener : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
listenForNotification()
//start listening
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
//stop listening
}
where listener would be something like this:
private suspend fun listenForNotification(){
withContext(Dispatchers.IO){
myApi.receive() {
println("this is my notification object: $it")
NotificationHelper.sendNotification($it.message)
}
}
}
And NotificationHelper would be based on Notification Manager to push local notifications (as you wanted them to slide from the top - look like any push notification). Pay close attention to the flags you use to send the notification to make sure it is received and processed by the currently opened activity (do more research on it, separate topic) https://developer.android.com/reference/android/app/NotificationManager
In that same activity use OnNewIntent to receive user's action of tapping on the notification and then do whatever you want to do with it.
Alternatively, do not use local notification but just develop your own UI where you would display these things messaging style. (edit: for example, like this - link. Another one for actually showing notifications without using Notifications lib -link
Or a combination of both local notifications and the above example.
Edit:
*You can also use Firebase messaging to display messages locally.* Yes you would still need a firebase json to init the app, but after that you can construct your messages locally and display them, so it a very lightweight dependency on two libs and aside from initializing you won't need anything else from the firebase server.
Below is an example with two types of messages, card and banner. And of course you can just take the full code on GitHub and extract the part you need and modify it as needed. (the method used here is public for testing the appearance of the message locally - I don't see anything wrong with using it as a vehicle to deliver local notifications, but again the option to take the code and modify is always there)
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.firebase.FirebaseApp
import com.google.firebase.inappmessaging.MessagesProto
import com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay
import com.google.firebase.inappmessaging.model.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
companion object {
val whiteHex = "#ffffff"
val magHex = "#9C27B0"
val appUrl ="app://open.my.app"
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val action: String? = intent?.action
val data: Uri? = intent?.data
data?.let {
helloTextView.text ="You just clicked from Firebase Message"
return
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseApp.initializeApp(this)
val text = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Body")
.build()
val title = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Title")
.build()
val imageData = ImageData.builder()
.setImageUrl("https://homepages.cae.wisc.edu/~ece533/images/frymire.png")
.build()
val button = Button.builder()
.setButtonHexColor(whiteHex).setText(text).build()
val campaignMeta = CampaignMetadata("S", "D", true)
val primaryAction = Action.builder()
.setActionUrl(appUrl)
.setButton(button)
.build()
val fmessage = CardMessage.builder()
.setPrimaryAction(primaryAction)
.setBackgroundHexColor(magHex)
.setPortraitImageData(imageData)
.setTitle(title).build(campaignMeta)
val bannerMessage = BannerMessage.builder()
.setAction(primaryAction)
.setImageData(imageData)
.setBackgroundHexColor(magHex)
.setBody(text)
.setTitle(title).build(campaignMeta)
FirebaseInAppMessagingDisplay
.getInstance()
.testMessage(this, bannerMessage, null)
}
}
In build.gradle make sure to add:
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.2'
and intent filter into manifest (to process click on the message)
<data android:scheme="app" android:host="open.my.app" />
also modify launchMode to singleTop to process the click within the same instance of the activity:
<activity android:name=".MainActivity"
android:launchMode="singleTop"
>
and apply
apply plugin: 'com.google.gms.google-services'
The result:
Card message:
Banner message and updating text in response to clicking on the banner:
Added project into GitHub if you are interested - project link. Must add your own google-services.json for firebase (to be able to init the engine only)

Related

Capability Info, Health Service, and Ambient Mode

I'm trying to write a fitness companion watch app that would collect heart rate, and calories via HealthServices API, and send them to the device, where we display a workout. I've been following suggested examples:
https://github.com/android/wear-os-samples/tree/main/AlwaysOnKotlin, https://github.com/android/health-samples/tree/2220ea6611770b56350d26502faefc28791f3cbd/health-services/ExerciseSample, and https://github.com/googlecodelabs/ongoing-activity .
I'm trying to achieve the following workflow:
Launch app on Wear device when X happens on the phone
Start exercise client on Wear
Send heart rate/calories update on a regular basis back to phone
Show summary screen, and stop exercise client when Y happens on the phone.
All of these work somewhat well until the watch goes into ambient mode. Then I run into the following problems:
When watch is in ambient mode, the capabilities client on the phone cannot locate watch, and tell it to start exercise. Nor can it tell it to stop exercise. What is a suggested workaround for this?
I use message client on phone to send message to the wearable. But nothing happens here, since the current node is empty.
currentNode?.also { nodeId ->
val sendTask: Task<*>? =
messageClient
?.sendMessage(nodeId, WORKOUT_STATUS_MESSAGE_PATH, "START.toByteArray())
When trying to simulate ambient mode by pressing 'hand' on the watch simulator, the ambient mode listener does not actually trigger to tell me the right thing. The screen gets "stuck" instead of updating to what I want it to.
Code for the ambient mode in MainActivity (I'm still learning Compose, so right now Main activity is where it's at, to eliminate other Compose specific errors):
In Manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
In Main Activity:
class MainActivity : FragmentActivity(), AmbientModeSupport.AmbientCallbackProvider {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ambientController = AmbientModeSupport.attach(this)
setContent {
val ambientEvent by mainViewModel.ambientEventFlow.collectAsState()
StatsScreen(ambientEvent)
}
...
}
override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = AmbientModeCallback()
inner class AmbientModeCallback : AmbientModeSupport.AmbientCallback() {
override fun onEnterAmbient(ambientDetails: Bundle) {
Timber.e("ambient event: enter: $ambientDetails")
mainViewModel.sendAmbientEvent(AmbientEvent.Enter(ambientDetails))
}
override fun onExitAmbient() {
Timber.e("ambient event: exit")
mainViewModel.sendAmbientEvent(AmbientEvent.Exit)
}
override fun onUpdateAmbient() {
Timber.e("ambient event: update")
mainViewModel.sendAmbientEvent(AmbientEvent.Update)
}
}
I don't see anything printed in this callback, and then consequently, by StateScreen doesn't really do anything when the device enters in the ambient mode.

Chromecast MediaIntentReceiver not receiving any intent when playback is pause/play from another device in same network

We use chromecast android sdk to cast some video files. we have our own implementation of play/pause/seek using RemoteMediaClient (play(), pause(), seek(Long)).
Also, we do allow chromecast to show notifications on the same device from which casting is happening using CastMediaOptions.Builder#setNotificationOptions(NotificationOptions).
To listen to notification controls we use CastMediaOptions.Builder()#setMediaIntentReceiverClassName(MediaIntentReceiver) which works completely fine when we control video from cast notification of the same device from which casting is done.
As we know Chromecast allows us to control casting media from all android devices in the same wifi network. when we try to play/pause the video from another device in same network MediaIntentReceiver won't receive anything and we are unable to change the player state inside our app.
Below is code for referance
OptionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setMediaIntentReceiverClassName(FitbuddMediaIntentReceiver::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setCastMediaOptions(mediaOptions)
.setStopReceiverApplicationWhenEndingSession(true)
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
MediaIntentReceiver (registered inside manifest too)
class FitbuddMediaIntentReceiver : MediaIntentReceiver() {
//this is getting call when playback toggle from same device's notification
//which is casting media but
//wont get call when playback is toggled from notification from another device
//inside same network.
override fun onReceiveActionTogglePlayback(currentSession: Session) {
"CAST -> onReceiveActionTogglePlayback ->".dumpError()
super.onReceiveActionTogglePlayback(currentSession)
//send event to active activity
}
}
We found that setMediaIntentReceiverClassName only give you event when interacting with notification which created by using CastMediaOptions.Builder()#setNotificationOptions().
So to check player state, when changed from other network device, we use RemoteMediaClient.addProgressListener(RemoteMediaClient.ProgressListener) which does not provide player state but does provide progress of cast receiver after given period. On onProgressUpdated we check for RemoteMediaClient.playerState so our ui is up to date with cast receiver state.

How To Record Calls In Android Devices Using Kotlin?

I want to make an Android application that can record both incoming and outgoing calls in the background as a service in kotlin and at a particular time in the day, it sends all that recordings to a server by API. I had researched about it all I found is to use Device Policy Manager and Telephoney Manager but it is not much about it on the internet. So can you help me with any article, documentation, or tutorial?
There is no solution from Google as of now. Google has deprecated the feature of recording the calls in it's latest versions of Android OS. Earlier it was possible, I had tried various methods but I was only getting the silent audio when I had tried to record calls. When using Google's Phone application it only allows that application to use the microphone and other things it won't allow any other application to overpower and get that hardware access.
But there are actually two hacks to do that.
Build your own phone application like Truecaller and manage every call and other things from that application by doing this you can get access to managing calls on your device and you will also get the access to record the calls.
If your work is specific to any one mobile example like Samsung, OnePlus, etc. Then you can use any Truecaller or Google's Phone application which will store the recordings of the calls in file storage and then you can make a service to upload that call recording from that particular file location every night at 12 AM or something.
first create MyCallRecordReceiver class
class MyCallRecordReceiver(callRecord: CallRecord) : CallRecordReceiver(callRecord) {
override fun onIncomingCallReceived(context: Context, number: String?, start: Date) {
super.onIncomingCallReceived(context, number, start)
}
}
then in MainActivity
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = MainActivity::class.java.simpleName
}
private lateinit var callRecord: CallRecord
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
callRecord = CallRecord.Builder(this)
.setLogEnable(true)
.setRecordFileName("CallRecorderTestFile")
.setRecordDirName("CallRecorderTest")
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setShowSeed(true)
.build()
}
fun StartCallRecordClick(view: View) {
LogUtils.i(TAG, "StartCallRecordClick")
callRecord.startCallReceiver()
}
fun StopCallRecordClick(view: View) {
LogUtils.i(TAG, "StopCallRecordClick")
callRecord.stopCallReceiver()
}
}
In addition Add it as a dependency in your app's build.gradle file
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
and this
compile 'com.github.aykuttasil:CallRecorder:1.5.3'

Synchronize coroutine from incoming messages

I'm using Android Kotlin with the SDK 30 and Coroutine 1.4.1.
I have a function that handles incoming messages to display them on my app in a form of temperature measurement. I use CoroutineScope to process the data and save it in the database. These messages are received from a socket.io connection. The problem is that the messages are not displayed in the correct order when a bulk of data is flowing in.
Now I've looked at my nodejs logs and these messages are sent in the correct order so it can't be that.
I'm using a standard Coroutine function.
See below.
fun receiveTmps(data){
CoroutineScope(IO).launch {
val usersJob = launch {
usersBg(data)
}
}
}
Now I know that with Coroutine I can add a join to wait for the job to finish before starting the next one. But because the messages do not come in at once, but flow continuously over a period of 5 to 20 seconds, it is possible that one message is completed faster than the older one. This causes incorrect order.
My question is, is there any way to handle these tasks 1 by 1 while adding multiple jobs to the list?
Any suggestion or idea is appreciated.
Thank you in advance.
UPDATED:
From what I read from the documentation you should also cancel the channel after its done. So that's going to be tricky to close the channel when messages are flowing in and since I don't have a clear number of what's flowing in I'm having a hard time defining that to the channel. I have tested several ways but most of the examples doesnt work or are outdated.
This is the most basic working example but it always has a defined repeat.
val channel = Channel<String>(UNLIMITED)
fun receiveTmps(data:String){
CoroutineScope(Dispatchers.Default).launch {
channel.send(data)
}
}
#ExperimentalCoroutinesApi
fun main() = runBlocking<Unit> {
launch {
// while(!channel.isClosedForReceive){
// val x = channel.receive()
// Log.d("deb", "Temperature.. "+ x)
// }
repeat(3) {
val x = channel.receive()
Log.d("deb", "Temperature.. "+ x)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
receiveTmps("10")
receiveTmps("30")
// Many more...
main()
}
If we need to process some events sequentially then typical solution is to create a queue of events and start a single consumer to process them. In the case of coroutines we can use Channel as a queue and launch a coroutine running in a loop that will be our consumer.
I'm not very familiar with Android, so I may miss something, but it should be something along lines:
fun receiveTmps(data:String){
channel.trySend(data).getOrThrow()
}
fun main() {
lifecycleScope.launch(Dispatchers.Default) {
for (tmp in channel) {
...
}
}
}
My assumption is that you want to stop processing events when the activity/service will be destroyed, ignoring all temps that are still waiting in the queue.

How do I get Nearby Messages to publish / receive from second activity?

I have got Nearby Messages apparently working fine exchanging messages happily between IOS and Android, but my Android app has multiple Activities. Once I switched to the second Activity the messages stop.
I have stripped out all the code in the Android app except for the line Nearby.getMessagesClient(this).subscribe(listener).
I then have a button to switch to a new instance of the same Activity. This works (messages are received from an IOS App that is just sending messages every 15 seconds) as a first Activity, but then fails (no messages received) once I click on the button and it starts itself.
Note that the onSuccessListner callback is triggered (the onFailureListener isn't). It thinks it is registered, but just doesn't get any messages.
I also did this with 2 copies of Activity with the same code, just to check that it wasn't because it was the same Activity class that it was failing. Still failed.
I took the Google sample App. This still uses the deprecated GooglaApiClient. It still gives the same result though.
I did spot that if the user has to take an action to enable the messages it works (as in the sample where the user has to switch messages on). I therefore tried adding a delay. A delay of 400 milliseconds means on my device that it works. 300 milliseconds and it still fails.
So I seem to have 2 choices. Add a spurious 1 second delay before enabling messages, or convert my App to use Fragments and a single Activity (I tried using the Application context and contrary to some documentation I found it tells me it must be an Activity Context). Neither are satisfactory workarounds, so am hoping that I have done something stupid.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fab.setOnClickListener { view -> // Switch to self.
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
mMessageListener = object : MessageListener() {
override public fun onFound(message: Message) {
Log.d(TAG, "Found message: " + String(message.content)) //Triggered ok until Activity switch
}
}
}
override public fun onResume() {
super.onResume();
uiScope.launch {// Using coroutines as a simple means of adding a delay. It also fails with no coroutine (and no delay)
delay(300) // This fails - but a value of 400 means that messages are received
Nearby.getMessagesClient(this#MainActivity2).subscribe(mMessageListener)
.addOnSuccessListener {
Log.e(TAG, "Success") // Always triggered
}.addOnFailureListener {
Log.e(TAG, "Failed " + it) // Never triggered
}
}
}
override fun onStop() {
Nearby.getMessagesClient(this).unsubscribe(mMessageListener)
super.onStop();
}
This is the code I am currently using, but as commented above it also fails with the old API, with Java, with no coroutines, etc. The Logcat is identical in the 300 or 400 ms cases.

Categories

Resources