Destroy Activity after leaving it - kotlin

I need to destroy an activity that have fragments with the login methods when i go to the mainactivity. I already tried flags, supportfragmentmanager but didn't works. Please, if anyone know how to do this tell me, please!
The signIn fragment is inside the AuthenticationActivity
Going to MainActivity Code:
val intent = Intent(context, MainActivity::class.java)
startActivity(intent)
When i go to the MainActivity, i need destroy the entire AuthenticationActivity, beucause i don't need that the users do login because they are already logged.

You need to call finish() on the Activity immediately after starting the other. To get the Activity instance in a Fragment, call requireActivity().
val intent = Intent(context, MainActivity::class.java)
startActivity(intent)
requireActivity().finish()

This is pretty simple to achieve. Just call finish after launching the intent.
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish() //this will destroy the activity immediately
EDIT:
As you're inside a fragment, you need your activity to handle your call. To do so, call:
requireActivity().finish()
More info here

Related

Push notifications with back stack (Pending Intents, Kotlin)

I'm facing a problem with passing the launch URL from one activity to another, without creating a new Intent for my MainActivity.
I have a webview, which is work with OneSignal push notifications. I wanted to modify the grouping notifications content.
If there's a way to get the result I want (modifying notifications group layout for OneSignal) That would be awesome. I'll simply use the One Signal default action and that would be the best solution for me.
If I have to implement it on that way:
https://developer.android.com/training/notify-user/group
The problem is, when I start a new child activity of the MainActivity, I don't use the "StartActivity / StartActivityForResults" functions.
This is the extension of OSRemoteNotificationReceivedHandler (OneSignal class)
It's outside of my MainActivity class.
class NotificationServiceExtension : OSRemoteNotificationReceivedHandler {
#RequiresApi(Build.VERSION_CODES.N)
override fun remoteNotificationReceived(
context: Context,
notificationReceivedEvent: OSNotificationReceivedEvent
) {
val notification = notificationReceivedEvent.notification
val bigText = Html.fromHtml(notification.body, FROM_HTML_MODE_LEGACY).toString()
var smallText = Html.fromHtml(notification.additionalData["cleantitle"] as String, FROM_HTML_MODE_LEGACY).toString()
val summaryStatistics = Html.fromHtml(notification.additionalData["setSummaryText"] as String, FROM_HTML_MODE_LEGACY).toString()
if (smallText == "test") {
smallText = Html.fromHtml(notification.additionalData["smalltitle"] as String, FROM_HTML_MODE_LEGACY).toString()
}
else{
val name = Html.fromHtml(notification.additionalData["text"] as String, FROM_HTML_MODE_LEGACY).toString()
smallText += " from $name"
}
val smallContent = RemoteViews("com.webviewapp.mywebviewapp", R.layout.small_layout_notification)
val sum = RemoteViews("com.webviewapp.mywebviewapp", R.layout.summary_layout_notification)
val bigContent = RemoteViews("com.webviewapp.mywebviewapp", R.layout.large_notification_layout)
bigContent.setTextViewText(R.id.notification_title, smallText)
bigContent.setTextViewText(R.id.notification_content, bigText)
smallContent.setTextViewText(R.id.notification_title, smallText)
sum.setTextViewText(R.id.notification_title, summaryStatistics)
notificationReceivedEvent.complete(null)
var bp: Bitmap? = null
try {
bp =Picasso.get().load(notification.largeIcon).get()
smallContent.setImageViewBitmap(R.id.noti_pic, bp)
bigContent.setImageViewBitmap(R.id.noti_pic, bp)
}
catch(e:Exception){
print(e)
}
try {
val fid = notification.additionalData["fid"] as String
notificationId = fid.toInt()
}
catch(e:java.lang.Exception){
notificationId += Date().time.toInt()
}
val notificationOpenActivity = Intent(context.applicationContext, MainActivity::class.java)
.putExtra("launchURL", notification.additionalData["pushURL"] as String)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context.applicationContext).run {
// Add the intent, which inflates the back stack
addNextIntentWithParentStack(notificationOpenActivity)
// Get the PendingIntent containing the entire back stack
getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT)
}
val receivedNotification = NotificationCompat.Builder(context.applicationContext, NOTIFICATION_GROUP)
.setSmallIcon(R.drawable.myIcon)
.setColor(ContextCompat.getColor(context.applicationContext,R.color.blue_primary))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setStyle(NotificationCompat.DecoratedCustomViewStyle()) // to expand button
.setAutoCancel(true)
.setVibrate(longArrayOf(500, 500, 500))
.setCustomBigContentView(bigContent)
.setCustomContentView(smallContent)
.setCustomHeadsUpContentView(sum)
.setChannelId(NOTIFICATION_CHANNEL)
.setGroup(NOTIFICATION_GROUP)
.setGroupSummary(false)
.setContentIntent(resultPendingIntent)
.build()
val summary = NotificationCompat.Builder(context.applicationContext, NOTIFICATION_GROUP)
.setSmallIcon(R.drawable.myIcon)
.setColor(ContextCompat.getColor(context.applicationContext,R.color.blue_primary))
//.setContentTitle(summaryStatistics.toString())
.setContentTitle(summaryStatistics)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setStyle(NotificationCompat.InboxStyle()
.setBigContentTitle(summaryStatistics)
.addLine(summaryStatistics)
.setSummaryText(summaryStatistics))
.setAutoCancel(true)
.setChannelId(NOTIFICATION_CHANNEL)
.setGroup(NOTIFICATION_GROUP)
.setGroupSummary(true)
.build()
NotificationManagerCompat.from(context.applicationContext).apply {
notify(notificationId, receivedNotification)
notify(SUMMARY_ID, summary)
}
}
}
And the notifications work good as I want. The problem is, How do I pass from the child activity the extra parameter to it's parent if I didn't create that child from the parent? I simply want to load it's URL into my webview, but also keep the back stack.
Also, how can I make sure I don't create multiple MainActivity if I won't use that child?
Thanks in advance.
Notification Back Stack
Android's documentation page "Start an Activity from a Notification" covers the back stack use case under the "Regular activity" suggestion.
Regular activity
This is an activity that exists as a part of your app's normal UX flow. So when the user arrives in the activity from the notification, the new task should include a complete back stack, allowing them to press Back and navigate up the app hierarchy.
I see you are using the addNextIntentWithParentStack method on TaskStackBuilder already in your code so looks like you may have already followed that page.
However there is one thing wrong with Google's docs here, the requestCode sent to getPendingIntent should be a unique value for your app.
Example:
getPendingIntent(
1234, // NOTE: Change this to a unique requestCode for your app
PendingIntent.FLAG_UPDATE_CURRENT
)
I have filed an docs issue with Google on this.
Lastly, since I didn't see this in your question make sure you have correctly added android:parentActivityName to your Activity in your AndroidManifest.xml per Android's "Define your app's Activity hierarchy"
OneSignal Details
Notification Tracking
Note that calling notificationReceivedEvent.complete(null) means OneSignal won't know anything about your notification you're displaying with NotificationManagerCompat. This changes a few things:
Click counts won't be sent to OneSignal
Notification won't be restored. (notifications are automatically cleaned when the app is "force stopped", device is rebooted, or app is updated)
Notification Groups
OneSignal can already do the grouping and summary you have in your code. Just set the "Group Key" on the dashboard, or android_group if you are sending the notification with the REST API.
Back stack
OneSignal doesn't allow you to control the back stack, it simply just always resumes that app and leaves the back stack un-effected. However you can disable this default behavior with com.onesignal.NotificationOpened.DEFAULT in your AndroidManifest.xml and use your own startActivity from the OneSignal.setNotificationOpenedHandler.

Android RecyclerView NotifyDataSetChanged with LiveData

When instantiating the ListAdapter for my RecyclerView, I call:
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
}
which is what I've seen done in the Android Sunflower project as the proper way to submit LiveData to a RecyclerView. This works fine, and when I add items to my database they are updated in the RecyclerView. However, if I write
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
adapter.notifyDataSetChanged()
}
Then my adapters data observer always lags behind. I call
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
override fun onChanged() {
super.onChanged()
if (adapter.currentList.isEmpty()) {
empty_dataset_text_view.visibility = View.VISIBLE
} else {
empty_dataset_text_view.visibility = View.GONE
}
}
})
And empty_dataset_text_view appears one change after the list size reached zero, and likewise it disappears one change after the list size is non-zero.
Clearly, the observer is running the code before it has submitted it which in this case is LiveData queried from Room. I'd simply like to know what the workaround is for this: is there a way to "await" a return from my LiveData?

How to use savedInstanceState in onResume in Kotlin?

I have a query in Kotlin in which a CountDownTimer is running and and the activity goes to a pause state.
When it goes to the pause state I'm storing the remaining time of the CountDownTimer in onSaveInstanceState and the timer is stopped using cancel(). Now I want to resume the counter when the activity resumes. But how do I access savedInstanceState inside onResume for knowing the remaining time?
I tried saving the values to a private bundle,
private var bundle:Bundle?=null
.
.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
bundle?.putInt("time_left",remaining_time)
}
override fun onResume() {
super.onResume()
time = bundle!!.getInt("time_left");
}
.
.
I could have written it in onCreate or using onRestoreInstanceState, but if an activity doesn't go to the onCreate state from onPause state, and directly goes to the onResume state(like when a phone call comes) both onCreate and onRestoreInstanceState will not help. Then how can I update it through onResume?
You're missing in your onCreate
if (savedInstanceState != null) {
// Load your variable
}
here is a link more detailed: How to use onSavedInstanceState example please
I think if you want to get "time_left", then try and use getIntent().getExtras() in onResume

how to have loading in Kotlin

my MainActivity contains a ViewPager that loads 4 fragments, each fragment should load lots of data from the server.
so when my app wants to be run for the first time, it almost takes more than 3 seconds and the other times(for example, if you exit the app but not clean it from your 'recently app' window and reopen it) it takes almost 1 second.
while it is loading, it shows a white screen.
is there any way instead of showing a white screen till data become ready, I show my own image?
something like the splash page?
If you do long-running actions on the main thread, you risk getting an ANR crash.
Your layout for each fragment should have a loading view that is initially visible, and your data view. Something like this:
(not code)
FrameLayout
loading_view (can show a progress spinner or something, size is match parent)
content_view (probably a RecyclerView, initial visibility=GONE, size is match parent)
/FrameLayout
You need to do your long running action on a background thread or coroutine, and then swap the visibility of these two views when the data is ready to show in the UI.
You should not be directly handling the loading of data in your Fragment code, as Fragment is a UI controller. The Android Jetpack libraries provide the ViewModel class for this purpose. You would set up your ViewModel something like this. In this example, MyData could be anything. In your case it's likely a List or Set of something.
class MyBigDataViewModel(application: Application): AndroidViewModel(application) {
private val _myBigLiveData = MutableLiveData<MyData>()
val myBigLiveData: LiveData<MyData>() = _myBigLiveData
init {
loadMyBigData()
}
private fun loadMyBigData() {
viewModelScope.launch { // start a coroutine in the main UI thread
val myData: MyData = withContext(Dispatchers.Default) {
// code in this block is done on background coroutine
// Calculate MyData here and return it from lambda
// If you have a big for-loop, you might want to call yield()
// inside the loop to allow this job to be cancelled early if
// the Activity is closed before loading was finished.
//...
return#withContext calculatedData
}
// LiveData can only be accessed from the main UI thread so
// we do it outside the withContext block
_myBigLiveData.value = myData
}
}
}
Then in your fragment, you observe the live data to update the UI when it is ready. The below uses the fragment-ktx library, which you need to add to your project. You definitely should read the documentation on ViewModel.
class MyFragment: Fragment() {
// ViewModels should not be instantiated directly, or they won't be scoped to the
// UI life cycle correctly. The activityViewModels delegate handles instantiation for us.
private val model: MyBigDataViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myBigLiveData.observe(this, Observer<MyData> { myData ->
loading_view.visibility = View.GONE
content_view.visibility = View.VISIBLE
// use myData to update the view content
})
}
}

Open Android activity automatically on receiving notification

I have to launch the app on receiving notification. The following piece of code works fine when the app is killed and notification is received (i.e the code inside if condition). But when the app is running in foreground or background, multiple instances of the activity gets created(i.e snippet in else condition). It's not the MainActivity that has to be launched on receiving the notification, instead it's some other activity containing the broadcast Receiver.
I have added the following lines in the onMessage of GCMintentService class.
if (currentPackage.equalsIgnoreCase(context.getPackageName()
.toString())) {
broadcastMessage(context, message);
} else {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.setAction(Intent.ACTION_MAIN);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
}
In the activity, under onReceive method of BroadcastReceiver, i am starting the activity again.
private final BroadcastReceiver mHandleMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
startActivity(getIntent().setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
};
I also had this requirement in one of my application. We can achieve it if we call
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.setAction(Intent.ACTION_MAIN);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
broadcastMessage(context, message);
In the main activity use the following in the broadcast receiver that would receive the broadcasted message above.
WakeLock wakeLock = null;
KeyguardManager kgMgr = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
boolean locked = kgMgr.inKeyguardRestrictedInputMode();
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
if (!pm.isScreenOn()) {
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
| PowerManager.ACQUIRE_CAUSES_WAKEUP, "MyWakeLock");
wakeLock.acquire();
}
if (locked) {
Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
Personally I feel that this is not the best of the answers and also best of the ideas to open the app directly when received a notification as there will be many functions like onCreate onResume, will be triggered automatically, spoil the users work if they are in a really important work by opening another app directly, also we need to put a lot of flags or use any other method to manage the flow of the application, when user open the app, app comes from background, app opened due notification and all such cases. Avoid it as it spoils the whole user experience.