My Fragment doesn't call onAttach(context) method when it launched from AppCompatActivity.
Fragment creating in XML:
<fragment
android:id="#+id/toolbar"
class="package.MainToolbarFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="#layout/fragment_main_toolbar" />
But if I extends it from support.v4.Fragment, onAttach(context) call !
What could be the problem?
Of course, I can extend all fragments from v4.Fragment, but I don't want it. Is it bad practice?
Also project min sdk 14.
It's not called because this method has been added in API 23. If you run your application on a device with API 23 (marshmallow) then onAttach(Context) will be called. On all previous Android Versions onAttach(Activity) will be called.
http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)
Support libraries fragment is platform independent. Hence it works on all API versions.
While Google wants us to stop using deprecated API's
#Override
public void onAttach(Context context) {
super.onAttach(context);
...
Is so new that it isn't widely called. You need to also implement
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
...
For me they are identical but I like to KISS and introducing another support library tends to double my apk to about 1000kb. I only updated my SDK yesterday.
The reason the types are not interchangeable here, as they are in many instances, is that the method taking an Activity will still be called when an Activity is provided as they are both publicly visible and Activity is more specialised than (as a sub class of) Context so will take precedence.
In addition to the aforementioned comments, I believe it is important to note that if you are attempting to use the onAttach() to update data contained inside the fragment from the parent Activity, it is possible to run into issues when the collection variable inside the Activity is null or empty when the fragment is inflated. At some point inside your Activity's life cycle, your data model may change and need to be updated inside the fragment. You might attempt to get a reference to a fragment already inflated, but find as you step through your code that onAttach() never fires, even when using the override containing a Context or Activity object.
If you are attempting to create a listener for the fragment and initialize the listener from the onAttach() callback method, onAttach() will not fire unless you provide the tag parameter as shown below when adding the fragment to the Activity:
// in the Activity
getFragmentManager().beginTransaction()
.add(
R.id.fragmentContainer,
CustomFragment.newInstance(customDataSource),
CustomFragment.TAG // Must be passed in for the code below to work
).commit();
// Getting a reference to the fragment later on (say to update your data model inside the fragment (in onActivityResult())
CustomFragment fragmentDelegate = (CustomFragment) getFragmentManager().findFragmentByTag(CustomFragment.TAG);
fragmentListener.updateDataSource(customDataSource);
Related
I just saw that onActivityCreated() is going to be deprecated in future. I try to implement LifecycleOwner and LifecycleObserver pattern but I'm not quite sure about what I'm doing here.
I'm using NavigationComponent, which meens :
I have a MainActivity
I have a MainFragment, instanciated as the home fragment
I have multiple fragments that can be accessed from this home fragment
For some reasons I need to know when activity is created from all of these fragments (MainFragment and sub fragments)
From what I've seen until now, I need to :
In the MainActivity, getLifecycle().addObserver(new MainFragment()). And do this for all sub fragments (which is verbose for nothing)
In fragments, implements LifecycleObserver and
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void onCreateEvent() {
Timber.i("%s MainActivity created", TAG);
}
This seems to work well, but I have some questions :
The syntax addObserver(new MainFragment() disturbs me. It looks like we are creating a new fragment instance, while the fragment is normally instantiated with the navigation defined in the navGraph.
As I said before, if I have my MainFragment with 10 sub fragments, I'll have to declare 11 observers ? Weird
Do I have to clear these observers at some point in the activity lifecycle ?
What is the proper way to implement it ?
EDIT 1:
To answer the question why I need to know when the activity is created :
I need this because I need to access my MainActivity viewmodel (new ViewModelProvider(requireActivity()).get(ViewModel.class). To call requireActivity() or getActivity() I need to know when the activity is created (was easy with onActivityCreated()).
Databinding is implemented with my MainActivity and this viewmodel. The layout of this activity is hosting a loader to show when network requests are performed.
I can perform requests from the MainFragment and from the sub fragments. When I perform a request from one of these fragments I need to enable this loader view, and when I got datas back I need to hide this loader.
And yes, all these fragments are in the graph
You have never needed to wait for onActivityCreated() to call requireActivity() or getActivity() - those are both available as soon as the Fragment is attached to the FragmentManager and hence can be used in onAttach(), onCreate(), onCreateView(), onViewCreated() all before onActivityCreated() is called.
This is one of the reasons why onActivityCreated() was deprecated - it actually has nothing to do with the activity becoming available to the Fragment, nor does it have anything to do with the activity finishing its onCreate() (it, in fact, can be called multiple times - every time the Fragment's view is created, not just once after the first time the Activity finishes onCreate()).
As per the deprecation notice:
use onViewCreated(View, Bundle) for code touching the Fragment's view and onCreate(Bundle) for other initialization.
Those are the recommended replacements, depending on whether the code you had in onActivityCreated() was accessing the Fragment's views or not.
Once you realize that requireActivity() can be called in onAttach(), etc., the rest of the deprecation notice makes more sense:
To get a callback specifically when a Fragment activity's Activity.onCreate(Bundle) is called, register a LifecycleObserver on the Activity's Lifecycle in onAttach(Context), removing it when it receives the Lifecycle.State.CREATED callback.
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
// Register a LifecycleObserver on the Activity's Lifecycle in onAttach()
requireActivity().getLifecycle().addObserver(this);
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void onCreateEvent() {
// Remove the LifecycleObserver once you get a callback to ON_CREATE
requireActivity().getLifecycle().removeObserver(this);
// Then do your logic that specifically needs to wait for the Activity
// to be created
Timber.i("%s MainActivity created", TAG);
}
But, as mentioned above, this is not what you should be doing if you are trying to access a ViewModel at the activity level.
We utilize LiveData's observe method quite a bit in our fragments. A recent release of the androidx fragment sdk is leading to Android Studio marking instances of liveDataObject.observe(this) as incorrect in favor of liveDataObject.observe(getViewLifecycleOwner()) .
Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated(). (b/137122478)
https://developer.android.com/jetpack/androidx/releases/fragment
We are worried about implementing this change because we do not understand how the functionality of getViewLifecycleOwner() compares to that of using this, and whether it will cause a conflict with the use of this or this.getActivity() when setting up the ViewModel in the fragment.
Additionally, we use the Android Navigation component and noticed that when the user navigates to different fragments within the same activity, each fragment's onDestroyView() method is called, but not onDestroy()
Here is an example of our code in onViewCreated()
vm.getStemLengths().observe(this, stemLengths -> {
this.stemLengths = new ArrayList<>(Stream.of(stemLengths).map(stemLength ->
new SearchModel(Integer.toString(stemLength.getValue()))).toList());
});
Later, in onDestroyView()
vm.getStemLengths().removeObservers(this);
At the same time, depending on the fragment, the ViewModel that houses the LiveData is set up with one of the following:
vm = new ViewModelProvider(this.getActivity()).get(PrepareVM.class);
To persist the viewmodel across fragments in the activity.
Or:
vm = new ViewModelProvider(this).get(AprobacionVM.class);
If the VM does not need to be persisted outside of the current fragment
So to summarize, will changing this to getViewLifeCycleOwner() when observing LiveData objects in fragments' onCreateView() conflict with the ViewModel patterns / Navigation component? Could there be an instance for example where the livedata change ends up triggering an observer from a previous fragment in the same activity that a user navigates away from?
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView(). Is that the correct understanding?
Fragment implements LifecycleOwner which maps its create and destroy events to the fragment's onCreate and onDestroy, respectively.
Fragment.getViewLifecycleOwner() maps its create and destroy events to the fragment's onViewCreated and onDestroyView, respectively. The precise sequence is described here.
If you're working with views in your observers, you'll want the view lifecycle. Otherwise you might get updates when the view hierarchy is invalid, which may lead to crashes.
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView().
Correct.
There is already a bug with it. If you do a new Intent for a fragment already use it gives getView is null error. Why? The activity is newly creared.
After updating the SDK to API level 23, I found that onAttach (Activity activity) is deprecated and the new method is onAttach (Context context). can any one enlighten me on why this change was made?
I think it has basically been to expand the scope of the method, but the official changelog doesn't say anything about it.
As you can see in the changelog they have removed the void onAttach(Activity) but they added a new one with the same name, and it says that is deprecated in the Android Official Documentation.
As richq commented, the support version of Fragment also deprecates onAttach(Activity) and has an onAttach(Context) that can be used instead on all Android versions right back to prehistoric ones.
To adapt to this new changes you can follow this steps:
Change the argument type of onAttach callback from Activity to Context. For unknown reason, this modification results in the fact that the method onAttach(Context) is not called anymore during the fragment lifecycle.
Move the code that was in onAttach method to onCreate one since it gets still executed.
With this modification, the app turns to run as before. No additional import statements are required.
Until this change happened, a fragment could only be attached to an activity. After this change Google can work towards attaching fragments to Services too. Something like how Facebook chat heads work, they could have fragment floating outside an activity too.
In creation of a fragment, I encountered getActivity() to be null.
So to narrow down the problem, I kept a local copy of activity in onAttach(Activity activity), which by definition is when it is attached to an activity.
However, I logged the activity in onAttach, and it is still null.
I'm only running into this problem in 2.3.6 and below.
Is this a known problem with support package?
The series of methods called to bring a fragment up to resumed state are:
onAttach(Activity) called once the fragment is associated with its activity.
onCreate(Bundle) called to do initial creation of the fragment.
onCreateView(LayoutInflater, ViewGroup, Bundle) creates and returns the view hierarchy associated with the fragment.
onActivityCreated(Bundle) tells the fragment that its activity has completed its own Activity.onCreate().
onViewStateRestored(Bundle) tells the fragment that all of the saved state of its view hierarchy has been restored.
onStart() makes the fragment visible to the user (based on its containing activity being started).
onResume() makes the fragment interacting with the user (based on its containing activity being resumed).
The bold method should be the one where getActivity doesn't return null anymore.
the onAttach method should not be used to call methods of the activity object, It should be used to initialise callback interfaces. An example of these interfaces can be found here.
This problem is because of the support package it means the fragment are from android 3.0 and up that is API level 11 and UP so for sure you will face app crash for android 2.3.6 gingerbird
this.getActivity();
The Android documentation suggests that to communicate from an activity to a hosted fragment, the fragment can define a callback interface and require that the host activity implement it. The basic pattern involves implementing onAttach in your fragment, and casting the activity to a callback inteface. See http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity.
Here's an example of providing a fragment some initialization data, as well as listening for a navigation callback.
public class HostActivity extends Activity implements FragmentHost {
#Override
UiModel getUiModel() {
return mUiModel;
}
#Override
FragmentNavListener getNavListener() {
return mNavListener;
}
...
}
public class HostedFragment extends Fragment {
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentHost) {
FragmentHost host = (FragmentHost) activity;
setUiModel(host.getUiModel());
setNavListener(host.getFragmentNavListener());
}
}
...
}
Compare this to using onAttachFragment in the host activity to explicitly initialize the fragment:
public class HostActivity extends Activity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof HostedFragment) {
HostedFragment hostedFragment = ((HostFragment) fragment);
hostedFragment.setUiModel(mUiModel);
hostedFragment.setNavListener(mNavListener);
}
}
...
}
To me, it seems like the first pattern has some drawbacks:
It makes the fragment harder to use from different activities, since
since all of those activities must implement the required interface. I can imagine cases where a given fragment instance doesn't require being fully configured by the host activity, yet all potential host activities would need to implement the host interface.
It makes the code slightly harder to follow for someone unfamiliar with the pattern being used. Initializing the fragment in onFragmentAttached seems easier to follow, since the initialization code lives in the same class that creates the fragment.
Unit testing using a library like Robolectric becomes harder, since when calling onAttach, you must now implement FragmentHost rather than just calling onAttach(new Activity().
For those of you who've done activity to fragment communication, what pattern do you find preferable, and why? Are there drawbacks to using onAttachFragment from the host activity?
I cant speak personally with respect to testing but there is alternatives to fragment / activity callback interface communication.
For example you can use a event bus to decouple the fragments and your activity. An excellent event bus can be found here:
Otto - An event Bus by Square
It is actively being developed by some very talented engineers at Square.
You can also use the LocalBroadcastManager that is packaged in the Android Support Library.
LocalBroadcastManager
Eric Burke from square has a presentation where he mentions both which can be found here:
Android App Anatomy
Update : As per the latest guidelines we are supposed to use a sharedViewModel class to communicate between fragments and activities.
However if you don't use viewModels and use callbacks, you should still consider removing onAttachFragment callback as it is deprecated now.
Instead on onAttachFragment, the suggested way is to add a listener to your fragmentManager who is associated with that transaction using addFragmentOnAttachListener.
See the example below:
childFragmentManager.addFragmentOnAttachListener { _, fragment ->
if (fragment is RetryDialogFragment) {
//Do your work here.
}
}
You should also be mindful of where you add this listener. You most probably want to add this listener before you execute the fragment transaction and should make sure that you are not adding the listener more than once.
I have used the Fragment.onAttach(...) pattern in my last project. I see two advantages:
you can check early that the hosting activity implements the required interface and throw an exception if not
there's less risk of holding onto a reference of the hosting context after the fragment has been detached
In order to take advantage of 2., you must not store references to UiModel and NavListener, as you do in your code sample. Instead, whenever you want to interact with these instances, you should use code like ((FragmentHost) getActivity).getNavListener().onNav(this), or alternatively ((FragmentHost) getActivity).onNav(this). You could store the fragment host in a field which you set to null in onDetach(...) as a middle ground approach, if you want to avoid the constant casting.
I agree that initializing a fragment from the activity that creates it seems more intuitive.
Having said all that, I'm going to skip fragments altogether in my current project. The following post reflects the lessons learned from my last one pretty well: https://corner.squareup.com/2014/10/advocating-against-android-fragments.html