Custom StartActivity taking long time to load new Activity - android

I am trying to call a new activity from my current one dynamically. So I created an object class that taking activity, class, and bundle as optional.
object ActivityHelper {
fun start(context: Context, activity: Class<out BaseCompatActivity>, extras: Bundle? = null) {
val intent = Intent(context, activity)
extras?.let {
intent.putExtras(extras)
} ?: run {
intent.putExtra("flag", context.javaClass.getSimpleName())
}
context.startActivity(intent)
}
}
after that, I am calling this from all activities like this
ActivityHelper.start(this, Activity::class.java, extras)
But I noticed it makes the app loading time slower than before. Am I doing it correctly? or is it bad idea to start activity like this?

On the activity you are trying to load, hide views in layout by default. onResume you can delay and then set the view visibility to true. If your UI layout is complex or you are using some library for anim, it would take more time to start.

Related

Open a new Activity with chosen Fragment on click

this is what I want to achieve:
I'm in Activity A with couple options to choose from (buttons)
after clicking any of them, I want to be taken to Activity B
Activity B should contain a constant part (audio) and a Fragment with image and text, depending on the button you choose in Activity A.
Is it achievable? I tried to both startActivity and getSupportFragmentManager (etc.) as my onClick method but with no use, maybe there's another way?
In activity B create a method (static in java, companion in kotlin) to create an intent:
companion object {
const val ARG_FRAGMENT_TO_LOAD = "argFragment"
fun newIntent(context:Context, fragmentTagToLoad:String): Intent {
return Intent(context, ActivityB::class.java).apply {
putString(ARG_FRAGMENT_TO_LOAD, fragmentTagToLoad)
})
}
}
Then, in activity A you can use this intent to start activity B.
myButton?.setOnClickListener {
startActivity(ActivityB.newIntent(this, SomeFragmentToLoad.ARG_TAG))
}
Then, again in activity B you will receive this argument in the intent, so that you can handle it. It is typically done inside onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val fragmentTagToLoad = intent.getStringExtra(ARG_FRAGMENT_TO_LOAD) ?: ""
if(fragmentToLoad.isNotEmpty() {
// Use fragment manager to load fragment into container.
}
...
}
Finally, in activity B you can use the argument to load the desired fragment using a (usually support) fragment manager.
Of course you should define ARG_TAG as a String constant inside SomeFragmentToLoad.

How to retain fragment state, when it's reopened by the deep link?

I have one fragment page in android, the first time I open it, I get data from bundle and show. Now, when I go to web page from this page and come back by deep-link, I lose my current data
(in other words, I have to go to the web page for payment, after that, I have to come back to the current fragment by deep link without losing current data)
Recently, I've been faced with a the same problem. I'm still a bit newbie in Android, so it's possibly not the best way to do it, but it does work.
First, in the Android Manifest, we have to add the attribute android:launchMode="singleTop" to the Activity that's hosting the Fragment. This way, when we go back to the app via deep link, the old Activity will receive the Intent instead of creating a new instance.
Then, in the code of the hosting Activity, we use the onNewIntent() method to look for the existing attached Fragment. As Hossein Kurd pointed out in his answer, this step depends on how we are managing our Fragments. In my case, I'm using a NavHostFragment, so first I have to find that and then I can search for the Fragment I want to come back to (in my case, an instance of LoginFragment):
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && intent.action == Intent.ACTION_VIEW && intent.data != null) {
val navHostFragment = supportFragmentManager.fragments.find { fragment -> fragment is NavHostFragment }
navHostFragment?.let { navHostFrag ->
val loginFragment = navHostFrag.childFragmentManager.fragments.find { childFragment -> childFragment is LoginFragment }
loginFragment?.let { loginFrag ->
val bundle = bundleOf("uri" to intent.data)
loginFrag.arguments = bundle
val fragmentTransaction = loginFrag.parentFragmentManager.beginTransaction()
fragmentTransaction.show(loginFrag).commit()
}
}
}
}
This method will receive the Intent from the browser, with the corresponding data (in my case, the Uri). We wrap the data in a Bundle and pass that to the Fragment's arguments and then show() the Fragment.
Finally, in the onResume() method of the Fragment, we check if the arguments include the data we are expecting, then we can go on with our logic:
override fun onResume() {
super.onResume()
this.arguments?.get("uri")?.let { data ->
// Your logic with the data you received from the browser
}
}
You can use getInstance static method, Better way is update fragment from activity
and the solution depends on the way you call your fragments: SupportFragmentManager, NavController ...
get current fragment by view Id or ...
Kotlin:
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { fragment ->
// fragment.onActivityResult(requestCode, resultCode, boundle)
fragment.method(param1, param2)
}

How to finish several SingleInstance Activities?

I have several activities with launchMode SingleInstance. On log out i want to finish all activities and open launchScreen.
val intent = Intent(context, LauncherActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
(context as AppCompatActivity).finishAffinity()
context.startActivity(intent)
However if i press back at Launcher activity i am forwarded to previously launched activities with singleInstance mode
UPDATE 11/1/2018:
I've tested multiple approach to deal with it such as event propagation, intent flags, counting activity instances, etc. There is some weird scenarios such as starting multiple singleInstance activities sequentially. In this case, middle activities doesn't start at all (onCreate method isn't called) and after pressing back button, they'll be started. Therefore no one of the prior approaches works! As the issue is a bit strange, I tried to solve it using a bit strange way.
We maintain the logout state in a singleton object called LogoutHandler. It cooperates with a class LogoutAwareActivity which is inherited by all activities except LoginActivity because it should not be affected by logout mechanism. When the logout occurs, a flag is set in the LogoutHandler until the last child of LogoutAwareActivity has finished then clears the flag.
Here is an implementation of that:
LogoutHandler:
import java.util.*
object LogoutHandler {
private var isLogout = false
private var timerWatchDog: TimerWatchDog? = null
fun isLogout() = isLogout
fun onActivityDestroyed() {
if (isLogout) {
timerWatchDog?.refresh(Runnable {
isLogout = false
timerWatchDog = null
})
}
}
fun logout() {
isLogout = true
timerWatchDog = TimerWatchDog(500)
}
private class TimerWatchDog(private val delay: Long) : Runnable {
private var timer: Timer? = null
private var runnable: Runnable? = null
fun refresh(runnable: Runnable) {
this.runnable = runnable
timer?.cancel()
val timerTask = object : TimerTask() {
override fun run() {
Thread(this#TimerWatchDog).start()
}
}
timer = Timer()
timer?.schedule(timerTask, delay)
}
override fun run() {
runnable?.run()
}
}
}
LogoutAwareActivity:
import android.support.v7.app.AppCompatActivity
abstract class LogoutAwareActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
if (LogoutHandler.isLogout()) {
finish()
}
}
override fun onDestroy() {
super.onDestroy()
LoginHandler.onActivityDestroyed()
}
}
A concrete Activity:
class ActivityA : LogoutAwareActivity() {
// ...
}
Another concrete Activity:
class ActivityB : LogoutAwareActivity() {
// ...
}
Your logout function:
fun logout() {
val intent = Intent(context, LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
LogoutHandler.logout()
context.startActivity(intent)
}
Visual Result:
All of MainActivity, ActivityA, ActivityB and ActivityC are single instance.
Traversing between activities by pressing back button:
Going to LoginActivity then pressing back button:
Before launching splash screen add this line
ActivityCompat.finishAffinity(this)
I dont know what exactly you were trying to do but i got a feeling you could redesign your app differently to do it better.
Anyway to your question - I guess you can check onStart of the activities if the user has logged off, and if he did start a single instance launcher activity and close these activities with finish().
In my experience extending the Application class is the simpler and most effective way to store limited amount of data which needs to be shared among all the activities.
In your case you can create a class holding your login data and store its instance in your custom Application object, where it can be reached by all activities. They can check for login availability at start, subscribe for changes and get notified when they need to finish. The Application object itself can subscribe for changes and start the login activity if needed.
I have several activities with launchMode SingleInstance. On log out i want to finish all activities and open launchScreen.
Here is one way:
Have all the activities extend a custom BaseActivity.
You can use LocalBroadcastManager to send local broadcast (within your app), when the logout button is clicked.
Inside the base activity you can implement the listener. You can call finish() inside the listener.
So when user clicks the logout button, you send the local broadcast to all the activities open. Since all your activities extend the common BaseActivity, the listener gets called and the activities finish.
After sending the broadcast, you can open the intended LauncherActivity.
See How to use LocalBroadcastManager?
for more.
P.S: You can un-register the listener in onDestroy. Since activity is still present, onDestroy won't have been called. And if it has already been destroyed then you have one less activity to worry about.

kotlin startactivity with flags

I am trying to start an acitvity through Anko context using kotlin, But would like to use flags
override fun createView(ui: AnkoContext<MyActivity>) = with (ui) {
verticalLayout {
// load something
button ("Back") {
onClick {
// goes back to the previous activity
startActivity<PreviousActivity>()
}
}
}
}
I am raising an activity like so,
startActivity<PreviousActivity>()
How can I add the flags to reorder the activity to top
This did not work, get a type mismatch error
startActivity(intentFor<PreviousActivity>("id" to 5).singleTop())
https://github.com/Kotlin/anko/wiki/Anko-Commons-%E2%80%93-Intents
I think singleTop() is what you're looking for
startActivity(intentFor<SomeOtherActivity>("id" to 5).singleTop())
I solved it using so,
getContext().startActivity(intentFor<PreviousActivity>().addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT))
I use the following code to start an activity with FLAGS
val intent = Intent(this#home_paciente,LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
startActivity(intent)
finish()
The part of
this#home_paciente
is the context of the activity so you put this#activity_name or replace with applicationContext and
LoginActivity::class.java
is the new activity to launch

How to pass interface to constructor when creating an Intent

I have two Activities: the MainActivity starts the NewReminderActivity. The first one will be notified when a new reminder has been created. Therefore it implements the interface OnEventAddedListener.
Do I need to use serialization to add the MainActivity to the intent or is there a better solution? I've never seen any examples using serialization to accomplish this and I'm sure it's very common to pass an interface from one activity to another in order to communicate.
public class MainActivity extends Activity implements OnEventAddedListener {
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if(item.getItemId() == R.id.action_addReminder)
{
// NewReminderActivity c = new NewReminderActivity(this);
// Intent intent = new Intent(this, c.getClass()); // this won't work
Intent intent = new Intent(this, NewReminderActivity.class);
startActivity(intent);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
}
You absolutely should not try to pass one activity to another, whether it's by serializing it (which won't even work for a number of reasons) or setting a reference.
Android will take care of cleaning up old activities out of memory, but won't be able to do so as long as you're holding on to a reference from it. Never hold on to other activities or fragments outside of their context!
You should follow the documentation on starting activities and getting results by using startActivityForResult() and provide that activity's result through onActivityResult(int, int, Intent).

Categories

Resources