Open a new Activity with chosen Fragment on click - android

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.

Related

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)
}

Pass data from BottomSheetFragment to previous Fragment

Is there any better way to send back the data to the previous fragment/parent fragment other than listener?
I have a fragment which consists of list of items. Clicking on the items will open a bottom sheet fragment. While closing the bottom sheet popup I need to pass data back to the fragment itself.
What I have done so far is created a listener and implemented it.
It really depends on what components you're using. If you are using Android Jetpack components then check out this article: LINK
You should be able to pass data back and forth similar to passing data with startActivityForResult()
Also, while you're at it please check out the official documentation too, there's a good example that will help you understand this better: LINK
Although you mention any way other than listener, but according to
documents:
Starting with Fragment 1.3.0-alpha04, each FragmentManager
implements FragmentResultOwner. This means that a FragmentManager can
act as a central store for fragment results. This change allows
components to communicate with each other by setting fragment results
and listening for those results...
Sets the FragmentResultListener for a given requestKey. Once the given
LifecycleOwner is at least in the STARTED state, any results set by
setFragmentResult using the same requestKey will be delivered to the
callback. The callback will remain active until the LifecycleOwner
reaches the DESTROYED state or clearFragmentResultListener is called
with the same requestKey.
To pass data back to fragment A from fragment B, first set a result listener on fragment A, the fragment that receives the result. Call setFragmentResultListener() on fragment A's FragmentManager, as shown in below:
in your BottomSheet Class:
btncloseBottomSheet.setOnClickListener {
val result = Bundle().apply {
// put your data in bundle
putInt("MY_KEY", 6)
}
setFragmentResult("requestCode", result)
dismiss()
}
in your previous fragment/parent fragment, you need to implement FragmentResultListener:
class PreviousFragment : FragmentResultListener {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set fragment listener
parentFragmentManager.setFragmentResultListener(
"requestCode",
viewLifecycleOwner,
this
)
}
...
// get result from other fragments by FragmentResultListener
override fun onFragmentResult(requestKey: String, result: Bundle) {
when (requestKey) {
"requestCode" -> {
val resultFromBundle = result.getInt("MY_KEY")
// Do somthing
}
}
}
}

How to pass data as intent from child activity to parent activity

I have an Activity -> MasterActivity and one more Activity ChildActivity which extends from Master Activity
Now the author of Master activity is expecting some values to come with the intent and parses these in the onCreate of Master activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view)
origUrl = intent.getStringExtra(EXTRA_URL)
if (origUrl == null) throw RuntimeException("did you forget to put extras?")
}
And I start the intent to my ChildActivity like:
context?.startActivity(Intent(context, TerminationWebview::class.java).apply {
putExtra(ChildActivity.EXTRA_URL, it)
Now the question is how do I pass this data to parent? So that parent can parse it in onCreate?
This question does not deal with returning data from an activity, it deals with passing the data to parent activity through intent and hence is not . a duplicate
You could do this by getting your intent data directly from the MasterActivity#onCreate
You might also want to change the tag to kotlin instead of java.

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.

Passing data back to previous fragment from current fragment

I am Using Navigation Drawer in my app. I have one MainActivity and rest of are Fragments. So the issue is Suppose i have three fragments like A,B,C.
Now in A i have one button and i am sending data from A>B.
For example putSring("datafrom A","datafrom A");
Now in B i receive data From A.
I have one button in B,and i am sending data from B>C.
For example putSring("datafrom B","datafrom B");
Now in C i receive data From B.
Then, I have one Button in C,and sending data from C>B.
For example putSring("datafrom C","datafrom C");
So,seems like in B i am getting data from two different fragments. I tried with all using activity and it work well with startActivityforresult. but how can i manager when all are fragments.
UPDATE
Starting with Androidx Activity 1.2.0-alpha02 and Androidx Fragment 1.3.0-alpha4, the official Android developer guide recommends to use the Activity/Fragment Result APIs over the deprecated Activity.onActivityResult(int, int, Intent) and Fragment.setTargetFragment(Fragment, int) methods:
it is strongly recommended to use the Activity Result APIs introduced in AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02.
Thus, to pass data back to fragment B from C, call setFragmentResultListener() on fragment B's FragmentManager, as shown in the following example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
}
In fragment C, set the result on the same FragmentManager by using the same requestKey using the setFragmentResult() API. Example:
setFragmentResult("requestKey", bundleOf("bundleKey" to "result"))
More details can be found at this guide.
The below answer is deprecated
You may call setTargetFragment() when you start the Fragment C from B. Example:
FragmentC fragmentC = FragmentC.newInstance();
fragmentC.setTargetFragment(FragmentB.this, REQUEST_CODE);
getFragmentManager().beginTransaction().replace(R.id.container, fragmentC).commit();
and then when you want to pass data back to fragment B from C, you can call the following code:
getTargetFragment().onActivityResult(
getTargetRequestCode(),
Activity.RESULT_OK,
new Intent().putExtra("datafrom C", "datafrom C")
);
and get it from the onActivityResult() method in your fragment B:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode==REQUEST_CODE && resultCode==Activity.RESULT_OK) {
String datafromC = data.getStringExtra("datafrom C");
}
}
When u are sending the data from Fragment A to Fragment B use the same boolean like below:-
FragmentA -> FragmentB
FragmentB ldf = new FragmentB ();
Bundle args = new Bundle();
args.putBoolean("BOOLEAN_VALUE",true);
ldf.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
And when u are send data from Fragment C to Fragment B use the same BOOLEAN which is used in Fragment A to B like below-
FragmentC -> FragmentB
FragmentB ldf = new FragmentB ();
Bundle args = new Bundle();
args.putBoolean("BOOLEAN_VALUE",false);
ldf.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
And in the last we have to check that value is recevied in FragmentB is from where like Fragment A OR FragemntC
FragmentB
Boolean getValue= getArguments().getBoolean("BOOLEAN_VALUE");
if(getValue)
{
//VALUE RECEIVED FROM FRAGMENT A
}
else
{
//VALUE RECEIVED FROM FRAGMENT C
}
Things changed a lot since 2017. The answer I post is basically an example from https://developer.android.com and it presents a good solution where your fragments, in any number, do not know anything about each other and still you are able to create a simple and elegant mechanism that can be used without much struggle.
The answer is based on ViewModels and LiveData.
Note: If you are not familiar with Architecture Components I strongly advise you to learn about it as much as you can any time you can as it will increase your production speed and decrease the number of errors in your projects.
Everything below is a citation from the following link: source (Kotlin/Java)
Share data between fragments
It's very common that two or more fragments in an activity need to
communicate with each other. Imagine a common case of master-detail
fragments, where you have a fragment in which the user selects an item
from a list and another fragment that displays the contents of the
selected item. This case is never trivial as both fragments need to
define some interface description, and the owner activity must bind
the two together. In addition, both fragments must handle the scenario
where the other fragment is not yet created or visible.
This common pain point can be addressed by using ViewModel objects.
These fragments can share a ViewModel using their activity scope to
handle this communication, as illustrated by the following sample
code:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
Notice that both fragments retrieve the activity that contains them.
That way, when the fragments each get the ViewModelProvider, they
receive the same SharedViewModel instance, which is scoped to this
activity.
This approach offers the following benefits:
The activity does not need to do anything, or know anything about this
communication.
Fragments don't need to know about each other besides
the SharedViewModel contract. If one of the fragments disappears, the
other one keeps working as usual.
Each fragment has its own lifecycle,
and is not affected by the lifecycle of the other one. If one fragment
replaces the other one, the UI continues to work without any problems.

Categories

Resources