Tracking Fragment Lifecycle like Application.ActivityLifecycleCallbacks - java

Everyone knows that in Android we can track Activities via Application.ActivityLifecycleCallbacks to obtain fires from system, when Activity created, stopped, destroyed, etc.
I found only one question on stackoverflow related to this theme.
Hooking into fragment's lifecycle like Application.ActivityLifecycleCallbacks
Unfortunately provided solution works only on post 25.2.0 Android.
I'm looking for soultion for pre 25.2.0. Maybe it could possible via some workarounds, reflection maybe?

I'm looking for soultion for pre 25.2.0
FragmentManager.FragmentLifecycleCallbacks was available from 25.1.0. The only change, that was introduced in 25.2.0 concerning this API is, that it became static, and before that it was just a public inner class. Which means in order to use you have to access it via its enclosing instance, which in this case is FragmentManager:
final FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentManager.new FragmentLifecycleCallbacks() {
#Override
public void onFragmentPreAttached(FragmentManager fm, Fragment f, Context context) {
super.onFragmentPreAttached(fm, f, context);
}
...
// all other callbacks
}, true);
As mentioned in Eugen Pechanec's comment, default framework fragments (i.e. android.app.Fragment, not from support packages) will receive these changes in Android-O release.

Related

How to handle communication between different viewModels?

I have an activity that contains a number of fragments. Each fragment has a viewModel scoped to its lifetime with some logic inside. The host activity has a viewModel too, including some code to show a popup-style message.
I want my fragment viewModels to be able to post messages to this popup. However, how could I access the activity-viewModel from inside one of my fragment-viewModels?
I'll write some exemplary Kotlin code, but the question is not specific to Kotlin since it's more of an architectural issue.
class MyActivityViewModel {
...
popupMessage = MutableLiveData<String>("") // is observed by my activity
fun postMessage(text: String) {
popupMessage.value = text
}
}
class MyFragmentAViewModel {
...
fun someFunctionA() {
// want to call ActivityViewModel's postMessage from here
}
}
class MyFragmentBViewModel {
...
fun someFunctionB() {
// want to call ActivityViewModel's postMessage from here too
}
}
I can't easily call ViewModelProvider since I'd rather not keep a reference to an Activity in my viewModel. The only direct option I see is to pass the activity-viewModel to the fragment-viewModels through the constructor or an init() method. That should be safe since the parent viewModel's lifetime should exceed the fragment viewModels' lifetime. I think.
Still, that solution rubs me the wrong way.
Are there any other alternatives? Or perhaps an entirely different approach to the issue?
Here's the thought of a greenhorn:
Can't you tell the activity that your fragment wants to use its method?
If you
Make an interface with a method a la "fragmentAWantsToUsePostMessage" in your fragment
Implement the interface in the activity, so that every time fragmentAWantsToUsePostMessage is called, the activity calls postMessage
Get a reference to the implementation of the interface in your fragment
Use that reference when the fragment needs to call "post message"
Shouldn't that work? Or is that against your "not keeping a reference"?
As I said: I'm new to all of this, so I might be completely wrong.
I can see that there's a post on medium that might be relevant: How to Communicate between Fragment and Activity

Send Object created from MainActivity to Fragments TabbedView [duplicate]

This question is mostly to solicit opinions on the best way to handle my app. I have three fragments being handled by one activity. Fragment A has one clickable element the photo and Fragment B has 4 clickable elements the buttons. The other fragment just displays details when the photo is clicked. I am using ActionBarSherlock.
The forward and back buttons need to change the photo to the next or previous poses, respectively. I could keep the photo and the buttons in the same fragment, but wanted to keep them separate in case I wanted to rearrange them in a tablet.
I need some advice - should I combine Fragments A and B? If not, I will need to figure out how to implement an interface for 3 clickable items.
I considered using Roboguice, but I am already extending using SherlockFragmentActivity so that's a no go. I saw mention of Otto, but I didn't see good tutorials on how to include in a project. What do you think best design practice should be?
I also need help figuring out how to communicate between a fragment and an activity. I'd like to keep some data "global" in the application, like the pose id. Is there some example code I can see besides the stock android developer's information? That is not all that helpful.
BTW, I'm already storing all the information about each pose in a SQLite database. That's the easy part.
The easiest way to communicate between your activity and fragments is using interfaces. The idea is basically to define an interface inside a given fragment A and let the activity implement that interface.
Once it has implemented that interface, you could do anything you want in the method it overrides.
The other important part of the interface is that you have to call the abstract method from your fragment and remember to cast it to your activity. It should catch a ClassCastException if not done correctly.
There is a good tutorial on Simple Developer Blog on how to do exactly this kind of thing.
I hope this was helpful to you!
The suggested method for communicating between fragments is to use callbacks\listeners that are managed by your main Activity.
I think the code on this page is pretty clear:
http://developer.android.com/training/basics/fragments/communicating.html
You can also reference the IO 2012 Schedule app, which is designed to be a de-facto reference app. It can be found here:
http://code.google.com/p/iosched/
Also, here is a SO question with good info:
How to pass data between fragments
It is implemented by a Callback interface:
First of all, we have to make an interface:
public interface UpdateFrag {
void updatefrag();
}
In the Activity do the following code:
UpdateFrag updatfrag ;
public void updateApi(UpdateFrag listener) {
updatfrag = listener;
}
from the event from where the callback has to fire in the Activity:
updatfrag.updatefrag();
In the Fragment implement the interface in CreateView do the
following code:
((Home)getActivity()).updateApi(new UpdateFrag() {
#Override
public void updatefrag() {
.....your stuff......
}
});
To communicate between an Activity and Fragments, there are several options, but after lots of reading and many experiences, I found out that it could be resumed this way:
Activity wants to communicate with child Fragment => Simply write public methods in your Fragment class, and let the Activity call them
Fragment wants to communicate with the parent Activity => This requires a bit more of work, as the official Android link https://developer.android.com/training/basics/fragments/communicating suggests, it would be a great idea to define an interface that will be implemented by the Activity, and which will establish a contract for any Activity that wants to communicate with that Fragment. For example, if you have FragmentA, which wants to communicate with any activity that includes it, then define the FragmentAInterface which will define what method can the FragmentA call for the activities that decide to use it.
A Fragment wants to communicate with other Fragment => This is the case where you get the most 'complicated' situation. Since you could potentially need to pass data from FragmentA to FragmentB and viceversa, that could lead us to defining 2 interfaces, FragmentAInterface which will be implemented by FragmentB and FragmentAInterface which will be implemented by FragmentA. That will start making things messy. And imagine if you have a few more Fragments on place, and even the parent activity wants to communicate with them. Well, this case is a perfect moment to establish a shared ViewModel for the activity and it's fragments. More info here https://developer.android.com/topic/libraries/architecture/viewmodel . Basically, you need to define a SharedViewModel class, that has all the data you want to share between the activity and the fragments that will be in need of communicating data among them.
The ViewModel case, makes things pretty simpler at the end, since you don't have to add extra logic that makes things dirty in the code and messy. Plus it will allow you to separate the gathering (through calls to an SQLite Database or an API) of data from the Controller (activities and fragments).
I made a annotation library that can do the cast for you. check this out.
https://github.com/zeroarst/callbackfragment/
#CallbackFragment
public class MyFragment extends Fragment {
#Callback
interface FragmentCallback {
void onClickButton(MyFragment fragment);
}
private FragmentCallback mCallback;
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt1
mCallback.onClickButton(this);
break;
case R.id.bt2
// Because we give mandatory = false so this might be null if not implemented by the host.
if (mCallbackNotForce != null)
mCallbackNotForce.onClickButton(this);
break;
}
}
}
It then generates a subclass of your fragment. And just add it to FragmentManager.
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), "MY_FRAGM")
.commit();
}
Toast mToast;
#Override
public void onClickButton(MyFragment fragment) {
if (mToast != null)
mToast.cancel();
mToast = Toast.makeText(this, "Callback from " + fragment.getTag(), Toast.LENGTH_SHORT);
mToast.show();
}
}
Google Recommended Method
If you take a look at this page you can see that Google suggests you use the ViewModel to share data between Fragment and Activity.
Add this dependency:
implementation "androidx.activity:activity-ktx:$activity_version"
First, define the ViewModel you are going to use to pass data.
class ItemViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Item>()
val selectedItem: LiveData<Item> get() = mutableSelectedItem
fun selectItem(item: Item) {
mutableSelectedItem.value = item
}
}
Second, instantiate the ViewModel inside the Activity.
class MainActivity : AppCompatActivity() {
// Using the viewModels() Kotlin property delegate from the activity-ktx
// artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectedItem.observe(this, Observer { item ->
// Perform an action with the latest item data
})
}
}
Third, instantiate the ViewModel inside the Fragment.
class ListFragment : Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by activityViewModels()
// Called when the item is clicked
fun onItemClicked(item: Item) {
// Set a new item
viewModel.selectItem(item)
}
}
You can now edit this code creating new observers or settings methods.
There are severals ways to communicate between activities, fragments, services etc. The obvious one is to communicate using interfaces. However, it is not a productive way to communicate. You have to implement the listeners etc.
My suggestion is to use an event bus. Event bus is a publish/subscribe pattern implementation.
You can subscribe to events in your activity and then you can post that events in your fragments etc.
Here on my blog post you can find more detail about this pattern and also an example project to show the usage.
I'm not sure I really understood what you want to do, but the suggested way to communicate between fragments is to use callbacks with the Activity, never directly between fragments. See here http://developer.android.com/training/basics/fragments/communicating.html
You can create declare a public interface with a function declaration in the fragment and implement the interface in the activity. Then you can call the function from the fragment.
I am using Intents to communicate actions back to the main activity. The main activity is listening to these by overriding onNewIntent(Intent intent). The main activity translates these actions to the corresponding fragments for example.
So you can do something like this:
public class MainActivity extends Activity {
public static final String INTENT_ACTION_SHOW_FOO = "show_foo";
public static final String INTENT_ACTION_SHOW_BAR = "show_bar";
#Override
protected void onNewIntent(Intent intent) {
routeIntent(intent);
}
private void routeIntent(Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case INTENT_ACTION_SHOW_FOO:
// for example show the corresponding fragment
loadFragment(FooFragment);
break;
case INTENT_ACTION_SHOW_BAR:
loadFragment(BarFragment);
break;
}
}
}
Then inside any fragment to show the foo fragment:
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(INTENT_ACTION_SHOW_FOO);
// Prevent activity to be re-instantiated if it is already running.
// Instead, the onNewEvent() is triggered
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(intent);
There is the latest techniques to communicate fragment to activity without any interface follow the steps
Step 1- Add the dependency in gradle
implementation 'androidx.fragment:fragment:1.3.0-rc01'

onClickListener not working (properly) after ConfigurationChange in fragments

Introduction
Huh, this is a tough one. At least, I think so...
To clear things up:
I did not find any answers to my question after searching the internet (using Google).
Everything I found was about people setting up the onClickListener's for their View's wrong. I guess it is the same problem in my case, but none of the other problems matched mine.
This seems to be the same problem... It has no answers.
Setup
I've got three Fragment's set up together with a ViewPager in my AppCompatActivity.
viewPager = (ViewPager) findViewById(R.id.main_view_pager);
viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));
Handled by the SectionsPagerAdapter.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return MainTextHolder.newInstance(tabIndicatorOnClick);
case 1:
return PostWriter.newInstance(tabIndicatorOnClick);
case 2:
return TopPage.newInstance(tabIndicatorOnClick);
default:
return Error.newInstance();
}
}
#Override
public int getCount() {
return 3;
}
// ... more methods
}
In each of the Fragment's I have some content plus a custom TabIndicator.
(the following xml file is my View's for the indicator)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<View
android:id="#+id/fragment_divider_one"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="one" />
<View
android:id="#+id/fragment_divider_two"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="two" />
<View
android:id="#+id/fragment_divider_three"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="three" />
</LinearLayout>
And then I set an OnClickListener for each of those View's (dividers) in the Fragment's onCreateView(LayoutInflater inflater, ViewGroup Bundle savedInstanceState) method. The OnClickListener is already prepared in my Activity where I also instantiate my ViewPager so that I can change the current Item (Fragment/tab).
private final View.OnClickListener tabIndicatorOnClick = new View.OnClickListener() {
#Override
public void onClick(View view) {
if (view.getTag().toString().equals("one"))
viewPager.setCurrentItem(0, true); // second argument for smooth transition
else if (view.getTag().toString().equals("two"))
viewPager.setCurrentItem(1, true);
else if (view.getTag().toString().equals("three"))
viewPager.setCurrentItem(2, true);
}
};
I pass that OnClickListener to the Fragment's by putting it into my newInstance(View.OnClickListener tabIndicatorOnClick) method.
This is for one of the fragments. It is identical for the others!
public static MainTextHolder newInstance(View.OnClickListener tabIndicatorOnClick) {
MainTextHolder fragment = new MainTextHolder();
Bundle args = new Bundle();
fragment.putIndicatorTabIndicatorOnClick(tabIndicatorOnClick);
fragment.setArguments(args);
fragment.setRetainInstance(true);
return fragment;
}
My putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick) method is a Void in an Interface. It just applies the OnClickListener to the Class(Fragment).
#Override
public void putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick) {
this.tabIndicatorOnClick = tabIndicatorOnClick;
}
Does it work
Yes it does. It works perfectly fine... until a ConfigurationChange happens. In my case I tested it with changing the orientation.
The Problem
What happens after that ConfigurationChange is that everything goes normally. The OnClickListener gets applied to all View's in all Fragment's, but the tabIndicatorOnClick OnClickListener is a null object reference in the second and third Fragment.
So in PostWriter and TopPage there isn't even really an OnClickListener on the View's for what ever reason. But it gets better: In MainTextHolder Fragment the tabIndicatorOnClick is not a null object reference, but it does not change the ViewPager's Item anymore. It runs the code, but it does not scroll the Tab.
When turning on "Don't keep activities" ind Developer options of the device and leaving the app, all OnClickListener's are null object references. Even those in MainTextHolder.
Summary
After the ConfigurationChange the OnClickListener gets passed into my first Fragment, but it is no working properly and in the remaining Fragment's it is not even passed / a null object reference.
The issue can be replicated through destroying the Activity and then reloading it.
In the end...
... I have no clue what is going wrong. I hope that I structured my question properly.
May be of interest
minSdkVersion: 14
targetSdkVersion: 25
I already have a SpringIndicator on my ViewPager. This does not feature an OnClickListener so I added my own "overlayed" indicators which have the needed OnClick feature. I also like the look of it, but if you can give me a better solution for my TabIndicator, I would also love to have that as an answer.
I came across the solution to put the method into the xml :onClick attribute multiple times, but for that I would need to create the same OnClickListener for every Fragment which is not really the nice way and it is also not solving my problem, but a way around it. - I would need to pass the ViewPager into my Fragment and then call that method from xml which is, as I said, just another way of doing it and not the way I prefer to do it. Also I don't know if it works. I'll most likely test it.
Other OnClickListener's in the Fragment still work properly. So as I said the problem lays in the OnClickListener itself not working right/being a null object reference.
Firstly, well done for writing a detailed description of the problem. Many questioners on StackOverflow can learn from the ability of this question to articulate the problem.
This error would seem to come from not accounting for Fragment lifecycle changes. While inside a ViewPager, Fragments will pass through various states including hidden and shown when they are temporarily offscreen, and paused and resumed if they are offscreen and the system frees memory, and finally created and destroyed if the Android system decides to save the instance state of your Activity (such as from a configuration change). In the latter case, Android will try and restore the state of your Fragments from saved instance state in your ViewPager. Since your Fragments have no way to save the View.OnClickedListener they were passed in the arguments, they end up with a null pointer when they are restored which is causing your error.
To fix the error, I suggest that you do not pass the View.OnClickedListener as a parameter to the Fragments. Rather, expose the OnClickListener to your Fragments via a public method in your Activity and have the Fragments get it themselves in their onResume(). This way you can guarantee that the Fragments will have a reference to the View.OnClickedListener whenever they are in a resumed state. So your onResume() might look something like this:
#Override
public void onResume() {
OnClickListener onClickListener = ((MyActivity) getActivity).getOnClickListener();
putIndicatorTabIndicatorOnClick(onClickListener);
}
Since you have set it in onResume(), you should remove it in onPause():
#Override
public void onPause() {
//set the onClickListener to null to free up the resource
}
Using onResume() and onPause() like this to free up resources from listeners is the approach recommended in the developer guide.
Of course, you will have to make sure your Activity also handles its own lifecycle by checking for saved state in onCreate(Bundle savedInstanceState) and restoring it appropriately.
Please note that saving instance state and restoring can be triggered in a number of ways:
Configuration state change (such as rotating the phone and causing the Activity to display in landscape rather than portrait
The device becoming low on memory (for example, if a large number of Activities are in recent apps)
Turning on Developer Options / Don't keep activities and using home to put your application in recent apps and then navigating back to your app.
You will be able to use one of these methods to determine if you have correctly handled the lifecycles of your Activities and Fragments.

Inject Context to activity

I am combining a static code analysis with a dynamic one. I basically create a new activity and set it up as the starting activity. During the run various methods in existing activities should be called.
I can call e.g. onCreate from outside, however, the super call to Activity will fail (or calls to SharedPreferences or other interesting classes) since Android does some initialization stuff when using the intents in order to call an activity (e.g. setting the context). But I need to somehow call methods like onCreate or onPause from outside while giving the target activity a valid context.
In my newly created activity I have got a valid context. I tried to pass it via calling ContextWrapper.attachBaseContext, but there is still a NullPointerException somewhere in Android due a the missing context. Is there some way to hack this somehow into a working state? Using reflection or other hacks would be no problem, since it is for analysis purposes only.
Thank you very much for any tips. I'd be able to modify the analyzed apps in any way to get this working.
However: Using an Intent is no option, since I cannot control which Activity-methods are being called, when and how often. I know that android has not been made for calling these methods directly, but it is not a common use case either :);
I have created a hack, which seems to help (I can get a valid context in the hacked activity). Let's see how far I get using this.
public static void hack(Activity hack, Activity main) {
try {
Field mActivityInfo = getField(Activity.class, "mActivityInfo");
mActivityInfo.set(hack, getClass("android.content.pm.ActivityInfo").newInstance());
Field mFragments = getField(Activity.class, "mFragments");
Field mContainer = getField(Activity.class, "mContainer");
Field mApplication = getField(Activity.class, "mApplication");
Field mWindow = getField(Activity.class, "mWindow");
Class FragmentManagerImpl = getClass("android.app.FragmentManagerImpl");
FragmentManager manager = (FragmentManager) mFragments.get(hack);
mApplication.set(hack, main.getApplication());
mWindow.set(hack, main.getWindow());
Class<?> FragmentContainer = getClass("android.app.FragmentContainer");
Method attachActivity = getMethod(FragmentManagerImpl, "attachActivity", Activity.class, FragmentContainer, Fragment.class);
attachActivity.invoke(manager, hack, mContainer.get(hack), null);
Method attachBaseContext = getMethod(ContextWrapper.class, "attachBaseContext", Context.class);
attachBaseContext.invoke(hack, new HackContext(main));
System.out.println("Hack performed");
} catch (Exception e) {
e.printStackTrace();
System.err.println("Hack failed :(");
}
}

Static Context from Activity.onStart()

I am trying to generate a notification from a class, Utilities.java, outside of the subclass of Context. I've thought about providing a SingletonContext class and have looked at posts ike this. I'd like to be able to return != null Context object since the notification can be generated at any given time because it is generated from a messageReceived() callback.
What are there downsides to doing something like this:
public static Context c;
public class MainActivity extends Activity{
#Override
public void onStart()
super.onStart()
c = this.getApplicationContext();
}
//other method somewhere outside this class
public Context getContext(){
return MainActivity.c
}
I don't think it would be any different than putting this on the onCreate(), however, it guarantees that the context is up to date when the activity starts.
The Context keeps a reference to this activity in memory, which you might not want. Perhaps use
this.getApplicationContext();
instead. This will still let you do file IO and most other things a context requires. Without a specific reference to this activity.
Maybe you should overwrite the onResume Method.
If you open a new activity, and switch back, the onStart method will not getting invoked.
Android Lifecycle: doc
BTW: I read about problems with ApplicationContext using a dialog or toast, so if you use the context to create on of these you should use your Activity as context.

Categories

Resources