Android Fragment in Layout Losing Listener - android

An interesting issue here. Writing an app in Kotlin (which is awesome btw) and due to customer design restraints we've had to implement a custom navigation drawer(wish we could use the native navigation view with app:menu but we can't).
Embedded fragment
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="false">
<fragment
android:name="com.redacted.app.nav.NavDrawerFragment"
android:id="#+id/nav_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.NavigationView>
// our callback
interface NavDrawerListener {
enum class CurrentState {
StateOne,
StateTwo
}
fun onStateOneClicked()
fun onStateTwoClicked()
}
In our case, we've created a Fragment call NavDrawerFragment that contains a RecyclerView with items and a callback interface that get hooked into in the fragment's onAttach(context: Context) method that lets the activity know the item that was clicked and any additional payloads needed to start the next activity. All works as expected, with the new activities using the same base layout and the fragments listener being implemented by the activity until... the back button is pressed. It appears that pressing back on Activity B calls onDetach on the fragment and when ActivityA resumes it the fragment instance's onAttach method never gets called again.
Am I missing something about fragments being embedded into a layout or is this behavior expected? All I need at the end of the day is for ActivityA's implementation of NavDrawerListener to be valid onResume().

I just went through something similar, as in weird fragment behavior. I resolved all the weird issues and errors by calling
SupportFragmentManager
The fragment itself was then still acting weird till I realized that it imported the regular fragment. Once I changed it to v4 fragment import all the weird quirks went away.
This might not help but hope it does.

Ok it turns out I quite misunderstood exactly how variables/values behave inside a companion object in kotlin. They are definitely static in nature. So what it seems actually happened was that when activity B finished and brought activity A back to the foreground that the detach method was setting the global companion object listener value to null so activity A could not access it.
Final Answer: make the listener interface variable a property of the instance vs a companion object member.
Thanks again for all the help and insight!

Related

Call different Fragment function in Activity in Android

The code is in the Android Studio template.
Android Studio - Create new project - Navigation Drawer Activity
There are an Activity and three Fragments (HomeFragment, GalleryFragment, SlideshowFragment) in the template project. I use an AppBarConfiguration object to manage three Fragment. Now I create a function in each Fragment.
My title may be a bit vague. My problem is how to execute the method in the current fragment by clicking the button in Activity.
Now my solution is as follows.
In each fragment. Get the activity object and find the button. Then set the click method. Here is my Kotlin code.
activity?.findViewById<FloatingActionButton>(R.id.fab)?.setOnClickListener { view ->
myFunction()
}
It works fine. But I think this is not elegant enough. And there are some problems with this. If I only set the click event of one fragment, when I switch to another fragment, the click event just now will be executed after I click the button.
I guess the reason for the above problem is that the click event of the activity is bound by the fragment.
My other solution is as follows.
Create an ActivityViewModel and set a LiveData value. Change value after clicking the button. Get ActivityViewModel in fragment and observe the value in each fragment. Then execute the method when the value is changed
My third solution is as follows.
Get each fragment object and determine whether it is visible. Then perform the function of the visible fragment. Here is my Kotlin code.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
var fragment1 = navHostFragment!!.childFragmentManager.fragments[0] as HomeFragment
var fragment2 = navHostFragment!!.childFragmentManager.fragments[1] as GalleryFragment
var fragment3 = navHostFragment!!.childFragmentManager.fragments[2] as SlideshowFragment
if(fragment1.isVisiable){
fragment1.myFunction()
}else if(fragment2.isVisiable){
fragment2.myFunction()
}else if(fragment3.isVisiable){
fragment3.myFunction()
}
But I still think the methods above are not elegant enough.
Can I use the interface to achieve the above functions? And how to achieve it?
I am not a native English speaker. Sorry for my bad English.
Thanks.

Fragment Recreation causes Observer to trigger onChanged() with Androidx Navigation library

Issue:
While working with Navigation Library, I observed when I navigate back to the previous fragment, it recreates the fragment and thus re-registering all my Observers which triggers OnChanged() again
I have a Snackbar which shows some error messages example if I am looking for no more data present or no Internet connection to the server:
deliveriesListViewModel.isMoreDataPresent.observe(this, Observer {
if (!it) showSnackBar(getString(R.string.no_more_data))
})
Source of above code here
And on navigating back and forth, the SnackBar pops up every time, and also every time I change the orientation or rotate my device.
My architecture has a single Activity with startDestination as my ListFragment in the navigation graph and a DetailFragment as destination. SupportNavigationUp or a simple OnBackPressed on DetailFragment returns me to my ListFragment and then recreates the fragment and thus re-registering all my Observers which triggers OnChanged() again and the SnackBar pops up when noMoreDataPresent LiveData is false
Now I tried the solution from here but unfortunately, it doesn't work
I have also tried to switch my LifecycleOwner to my activity by that also doesn't work.
Tried moving the ViewModelProviders.of to OnCreate and onActivityCreated - doesn't work
Please suggest corrections or any ideas what can be done to prevent SnackBar popping up after navigation and orientation change.
Footnotes
I have gone through these issues:
Multiple LiveData Observers After Popping Fragment
How to avoid fragment recreation when tap back button using navigation architecture actions?
Is there a way to keep fragment alive when using BottomNavigationView with new NavController?
here is my complete source code
This article, especially item 1, might be relevant to what you're experiencing. Basically, what happens is that you may have multiple spawned Observers every time you navigate back to your fragment, thus executing onChanged multiple times. Using the fragment's view lifecycle as the LifecycleOwner should prevent this from happening, so your code above would look like this:
deliveriesListViewModel.isMoreDataPresent.observe(viewLifecycleOwner, Observer {
if (!it) showSnackBar(getString(R.string.no_more_data))
})

Shared ViewModel to help communication between fragments and parent activity

While Navigation component of JetPack looks pretty promising I got to a place where I could not find a way to implement something I wanted.
Let's take a look at a sample app screen:
The app has one main activity, a top toolbar, a bottom toolbar with fab attached.
There are 2 challenges that I am facing and I want to make them the right way.
1. I need to implement fragment transactions in order to allow replacing the fragment on the screen, based on the user interaction.
There are three ways I can think of and have this implemented:
the callbacks way. Having a interface onFragmentAction callback in fragment and have activity implement it. So basically when user presses a button in FragmentA I can call onFragmentAction with params so the activity will trigger and start for example transaction to replace it with FragmentB
implement Navigation component from JetPack. While I've tried it and seems pretty straightforward, I had a problem by not being able to retrieve the current fragment.
Use a shared ViewModel between fragment and activity, update it from the fragment and observe it in the activity. This would be a "replacement" of the callbacks
2. Since the FAB is in the parent activity, when pressed, I need to be able to interact with the current visible fragment and do an action. For instance, add a new item in a recyclerview inside the fragment. So basically a way to communicate between the activity and fragment
There are two ways I can think of how to make this
If not using Navigation then I can use findFragmentById and retrieve the current fragment and run a public method to trigger the action.
Using a shared 'ViewMode' between fragment and activity, update it from activity and observe it in the fragment.
So, as you can see, the recommended way to do navigation would be to use the new 'Navigation' architecture component, however, at the moment it lacks a way to retrieve the current fragment instance so I don't know how to communicate between the activity and fragment.
This could be achieved with shared ViewModel but here I have a missing piece: I understand that fragment to fragment communication can be made with a shared ViewModel. I think that this makes sense when the fragments have something in common for this, like a Master/Detail scenarion and sharing the same viewmodel is very useful.
But, then talking between activity and ALL fragments, how could a shared ViewModel be used? Each fragment needs its own complex ViewModel. Could it be a GeneralViewModel which gets instantiated in the activity and in all fragments, together with the regular fragment viewmodel, so have 2 viewmodels in each fragment.
Being able to talk between fragments and activity with a viewmodel will make the finding of active fragment unneeded as the viewmodel will provide the needed mechanism and also would allow to use Navigation component.
Any information is gladly received.
Later edit. Here is some sample code based on the comment bellow. Is this a solution for my question? Can this handle both changes between fragments and parent activity and it's on the recommended side.
private class GlobalViewModel ():ViewModel(){
var eventFromActivity:MutableLiveData<Event>
var eventFromFragment:MutableLiveData<Event>
fun setEventFromActivity(event:Event){
eventFromActivity.value = event
}
fun setEventFromFragment(event:Event){
eventFromFragment.value = event
}
}
Then in my activity
class HomeActivity: AppCompatActivity(){
onCreate{
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromFragment.observe(){
//based on the Event values, could update toolbar title, could start
// new fragment, could show a dialog or snackbar
....
}
//when need to update the fragment do
viewModel.setEventFromActivity(event)
}
}
Then in all fragments have something like this
class FragmentA:Fragment(){
onViewCreated(){
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromActivity.observe(){
// based on Event value, trigger a fun from the fragment
....
}
viewModelFragment = ViewModelProviders.of(this, factory)
.get(FragmentAViewModel::class.java)
viewModelFragment.some.observe(){
....
}
//when need to update the activity do
viewModel.setEventFromFragment(event)
}
}

Update Activity title from fragment in FragmentPagerAdapter

I have v4.ViewPager inside Activity and use SlidingTabLayout from google's examples SlidingTabBasics. The problem I encounter is that each fragment retrieved from getItem(position) in v4.FragmentPagerAdapter has to refresh activity title. I have already learnt the hard way that FragmentPagerAdapter causes fragments to have really weird life callbacks so I can't probably use onResume or onStart. I noticed though that onCreateOptionsMenu(menu,inflater) gets called exactly when I want to refresh activity title. Is there a callback to supply actions when ViewPager has settled the fragment and it should change activity title?
Setting callback on ViewPager.onPageSelected(position) is inconvenient because I want this information to be propagated from fragment, not to fragment.
Currently I 'steal' onCreateOptionsMenu(menu,inflater) to do the work for me but it causes optimisation issues when no menu should be inflated but I still want the fragment to be able to affect activity title.
Have you tried this code in your fragment:
if(getActivity() != null){
getActivity().setTitle("new title");
}
Take into consideration that getActivity() will be null if the fragment is not yet attached to the activity.
Godspeed.
You can do this in different ways.
You can use interfaces in fragments that can be implemented by activity. But the drawback is, if you do have large number of fragments you must implement all of them.

Where to put the Fragment functional code?

Just a general question about working with Fragments and Activitys for android development: where does the business end of the functional code go for Fragments loaded into an Activity dynamically? (i.e. a fragment's OnClickListeners, OnCheckedChangedListeners, button logic methods...)
Do they go in the Fragment class, or the Activity class?
All the GUI logic for views attached to a fragment should be contained inside the fragment itself.
Thus a fragment should be as self contained as possible.
You can, though, if necessary do callbacks to your activity based on fragment GUI interaction. This can easily be done like this inside the fragment:
#Override
public void onAttach(Activity activity) {
if (!(activity instanceof SherlockFragmentActivity)) {
throw new IllegalStateException(getClass().getSimpleName()
+ " must be attached to a SherlockFragmentActivity.");
}
mActivity = (SherlockFragmentActivity) activity;
super.onAttach(activity);
}
In this specific case the reason for gaining a reference to SherlockFragmentActivity is to gain access to the support menu inflater mActivity.getSupportMenuInflater(), hence the construction can of course also serve to gain information from the underlying activity.
This probably depends on how much the Fragment's functionalities have in common, and how many, let's say Buttons, have to be handled.
I personally (and it's probably most common practice) handle onClick(...) events separately for each Fragment, meaning that I let each Fragment implement it's own OnClickListener.
Furthermore, when handling everything through the Activity, probably not all the components that react to click-events are in memory at all times and can be reached via findViewById(...), depending on which Fragment is currently displayed and how your user-interface is built up in general.
they always in fragment class because fragment is one type of component in android which we can reuse it. if we put onclick and oncheckchanged in activity then what meaning of reusing that component??
for more information about please go through following step:
Link 1 for basic level of information about fragment and how to handle them
Link 2 for dealing with multi pane fragment
Standard site for fragment
It depends:
If fragment can handle logic which is self sufficient(complete) then that code can be handled by fragment. e.g. on click call phone number.
If fragment have UI whose action is activity specific, then you want to add listener in activity.
e.g. master detail view like email client, on tablet user click on title fragment1 which have list of email titles, then handler on click in activity can show detail fragment2 in activity.
In all you want to keep fragment reusable.

Categories

Resources