I'm using singleton fragment. I thought when I call this singleton fragment, the lifecycle methods onCreateView and onActivityCreated will be called only once. But they aren't even though the fragment is singleton, onCreateView and onActivityCreated are called when I call fragment. But I found something strange. That is, the RecyclerView is holding it's position. If I move A frag(using RecyclerView, position at 20) to B frag and redirect to A frag, the A fragment position is 20. Although onCreateView and onActivityCreated are called again, Why the A fragment position is saved?
ps: I know kotlin support singleton class "Objcet". But I'm more comfortable using singleton constructor than object class.
MainActivity
navigation_view.setNavigationItemSelectedListener {
drawerLayout.closeDrawer(GravityCompat.START)
when(it.itemId){
R.id.scheduleFragment->{
changeFragment(scheduleFragment)
}
R.id.noticeFragment->{
changeFragment(NoticeFragment())
}
}
true
}
}
fragment
companion object {
var scheduleFragment: ScheduleFragment? = null
}
fun getInstance(context: Context): ScheduleFragment {
if (scheduleFragment == null) {
scheduleFragment = ScheduleFragment()
}
return scheduleFragment!!
}
Well, it's behaving exactly as expected. The recycler-view's view, position all are members to the fragment instance. So the values remain same as it has only single instance. But the life cycle methods have nothing to do with the fact that the fragment class is singleton. They get called when the specific event happens. For example, when the activity is created then the onActivityCreated method get called by the system and this method calling has nothing to do about the fragment instance creation. Because the fragment instance creation happens earlier when you make an instance of fragment. Now after the use either you want to keep the instance or destroy it, it's your choice. Hope this will clear your confusion. Let me know if you don't understand anything.
Related
I need to get the LifecycleOwner to pass it in my FirestoreRecyclerViewOption but Fragment doesn't implement Lifecycle.
So, how to get that?
My Fragment implements LifecycleOnwer as shown in the following code example,
public class ListRestFragment extends Fragment implements LifecycleOwner
after in the onCreateMethod,
lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
and to finish
#Override
public void onStart() {
super.onStart();
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
}
#NonNull
#Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
I follow this example
but it doesn't work
Attempt to invoke virtual method 'androidx.lifecycle.Lifecycle$State androidx.lifecycle.Lifecycle.getCurrentState()' on a null object reference
I need it to edit My FirestoreRecyclerViewOption and setLifeCycleOwner because it is always null and I have an NullPointer everytime.
In fragments you can use getViewLifecycleOwner() method to get the lifeCycleOwner.
Update: Actually it is better to use getViewLifeCycleOwner if you are using views in your observers. Because the view might not exist when the LiveData gets updated and you want to change the view(e.g. changing visibility of a view) which causes crashes.
The fragment itself is the LifecycleOwner you are looking for. You can use keyword "this" as a reference to the fragment.
Read this: "Lifecycle
is a class that holds the information about the lifecycle state of a component (like an activity or a fragment) and allows other objects to observe this state."
And:
"LifecycleOwner is a single method interface that denotes that the class has a Lifecycle."
This Google sample calls observe on LiveData in a fragment and passes getActivity() as the LifecycleOwner.
mSeekBarViewModel.seekbarValue.observe(getActivity(), new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
https://github.com/googlecodelabs/android-lifecycles/blob/master/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java
I can't wrap my head around any reasons to do that. I only want updates as long as the fragment is active, so why not scope it to the fragment?
Are there any reasons to ever NOT scope it to the fragment?
In this case, the Fragment is inflated from a <fragment> tag in the Activity's layout, so the lifecycle of the Fragment and the Activity is always the same so it doesn't make any difference.
However, there are two cases where this fails badly:
If you remove() or replace() the Fragment, using getActivity() for your LifecycleOwner will result in leaking the Fragment since the LiveData holds a strong reference to the Observer (and hence, the Fragment since it is a non-static inner class) until the Activity is destroyed
If you detach() and then attach() the Fragment (such as with a FragmentPagerAdapter), then using the Fragment's lifecycle in onCreateView() will result in multiple Observers since onCreateView() is called each time the Fragment's view is recreated upon attach and previous Observers are not destroyed since the Fragment's lifecycle has not been destroyed.
The correct LifecycleOwner to use in onCreateView() is always getViewLifecycleOwner() since this lifecycle is destroyed when the Fragment's View is destroyed:
mSeekBarViewModel.seekbarValue.observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
This prevents leaking the Fragment by using a potentially longer lifespan LifecycleOwner (like the Activity) and prevents multiple Observers being registered when using patterns like those employed by FragmentPagerAdapter.
Are ViewModels independent of activity/fragment lifecycles or just their configuration changes. When will they cease to exist and the subsequent onCleared() method called.
Can the viewModel be shared with another Activity ?
A situation:
Activity1+viewModel1--->(rotation)--->Activity1+viewModel1
--->(launch Intent)--->Activity2+viewModel1
is this sharing possible and is it a good practice.
Also, since the app lifecycle callbacks, onPause->onStop->onDestroy is same for both
1.activity rotating and
2.when an Activity ends,
how is a ViewModel figuring out internally the right time to call onCleared and finally end its lifecycle.
Findings:
the ViewModel uses a holderFragment internally to hold an instance of the activity and uses the setRetainInstance method like fragments to account for configuration changes.
Source: dive-inside-of-androids-viewmodel-architecture-components
Are ViewModels independent of activity/fragment lifecycles or just
their configuration changes.
ViewModels (VMs) are independent of configuration changes and are cleared when activity/fragment is destroyed.
Following is the lifecycle of ViewModel from official site:
Can the viewModel be shared with another Activity ?
You shouldn't do that with Activities. However fragments can share a ViewModel using their activity scope to handle communication between them
How is a ViewModel figuring out internally the right time to call onCleared and finally end its lifecycle?
A VM's onCleared is called when the app is put into the background and the app process is killed in order to free up the system's memory.
See the Do ViewModels persist my data? section from this Android Developer's post, ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders
If you want the user to be able to put the app into the background and then come back three hours later to the exact same state, you should also persist data. This is because as soon as your activity goes into the background, your app process can be stopped if the device is running low on memory.
If the app process and activity are stopped, then the ViewModel will be cleared as well.
Check method onDestroy() in Fragment.java
public void onDestroy() {
this.mCalled = true;
FragmentActivity activity = this.getActivity();
boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
if (this.mViewModelStore != null && !isChangingConfigurations) {
this.mViewModelStore.clear();
}
}
The variant isChangingConfigurations is true when the Activity rotates, the viewModelStore method clear() is not called.
When Activity is destroyed, isChangingConfigurations is false, the viewModelStore will be cleared.
Through the source code, we know the ViewModel binds with HolderFragment. you can from the code in class ViewModelProviders to find it.
#MainThread
public static ViewModelProvider of(#NonNull FragmentActivity activity,
#NonNull Factory factory) {
checkApplication(activity);
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}
next, in-class HolderFragment on it's onDestroy() you can find
#Override
public void onDestroy() {
super.onDestroy();
mViewModelStore.clear();
}
Last, open it,
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
now, maybe you have know it. just like the picture above. When the fragment finished, it cleared; when activity recreate,the fragment's onDestroy() will not be invoked, because
public HolderFragment() {
setRetainInstance(true);
}
hope it can help you.
If you follow the trail (Check super class)
AppCompatActivity --> FragmentActivity --> ComponentActivity
ComponentActivity observe the lifecycle state.
onDestory() calls at configuration change (such as screen rotation) but viewModel doesn't get destroy because of the following condition.
getLifecycle().addObserver(new GenericLifecycleObserver() {
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
I wanted my VM's onClear to be called when the Activity was finishing. I use onPause, because the call to onDestroy is not always immediately executed...it could be a few seconds after onPause:
class SomeActivity : AppCompatActivity() {
override fun onPause() {
super.onPause()
// viewmodel is not always cleared immediately after all views detach from it, which delays
// the vm's cleanup code being called, which lets the resources continue running
// after all UIs detach, which is weird, because I was using timers and media players.
// this makes the VM execute onCleared when its Activity detaches from it.
if (isFinishing) {
viewModelStore.clear()
}
}
}
And here is the order of execution in respect to the GenericLifecycleObserver:
onStateChanged()
onResume()/onDestroy()/etc.
Meaning the observer received the information about the pending state change before it's completed, so for exemple onDestroy() method is finished.
I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.
As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.
While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?
What I would do is
Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example:
if a fragment is not active yet:
Bundle args = new Bundle();
args.putParcable("businessObject", yourBusinessObjectThatIsParcable);
yourFragment.setArguments(args);
Otherwise:
yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);
In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.
When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);
in your last page or last fragment you can pass your object to your activity, here is how to do it
Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.
Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.
I wouldn't recommend a global singleton. There are two main reasons:
By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().
All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton
For example,
public class MyParentFragment extends Fragment {
private String mPageOneData;
private int mPageTwoData;
private List<Date> mPageThreeData;
public void setPageOneData(String data) {
mPageOneData = data;
}
...
}
public class PageOneFragment extends Fragment {
private void sendDataToParent(String data) {
Fragment f = getParentFragment();
if (f != null && f instanceof MyParentFragment) {
MyParentFragment parent = (MyParentFragment) f;
f.setPageOneData(data);
}
}
}
you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background.
you can restore your data in onCreate() event by using Bundle and getExtras().
you can save your data in application class and the data will still be there in case your process will go into the background.
i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments.
I hope i could help :)
Have you checkout EventBus?
I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.
Hope it helps
I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.
In your MainActivity add this interface:
public interface Updateable {
public void update();
}
Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:
public class FragmentOne extends Fragment implements MainActivity.Updateable {
...
#Override
public void update() {
// YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
if ( adapter != null ) {
adapter.notifyDataSetChanged();
} else {
Log.d("LOG_TAG", "null");
}
}
...
}
Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:
#Override
public int getItemPosition(Object object) {
if ( object != null && object instanceof FragmentOne ) {
FragmentOne f = (FragmentOne) object;
f.update();
}
return super.getItemPosition(object);
}
Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int previousState;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {
if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());
viewPagerAdapter.notifyDataSetChanged();
}
}
previousState = state;
}
});
Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.
You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using
Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
SharefPreference(preferably) - If you want to use single values.
In both cases you will create a singleton object and access its properties in your fragments.
make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.
it should motivate you
http://www.developerphil.com/parcelable-vs-serializable/
I'm dealing with fragments.
I have an Activity and different fragments.
Each fragment need the access to a Class(call it X) that allow it to access a database, but, because I have a lot of fragments, I don't want to create a different instance of the Class X in every fragment as I think it will require lots of memory.
So how can I do?
I wrote something like this (with a getter), but it doesn't work!
public class MyActivity {
private ClassX classx;
.....
public ClassX getClassX() {
return classx;
}
.....
}
But than, how can I call it from the fragment?
From the fragment call your activity's method
((MyActivity ) getActivity()).getClassX() ;
This is a little bit more of a Java question and android.
If you looking at accessing the database, look at creating a database singleton.
So something like:
public class Database {
// This starts off null
private static Database mInstance;
/**
* Singleton method, will return the same object each time.
*/
public static final Database getInstance() {
// First time this method is called by Database.getInstance() from anywhere
// in your App. It will create this Object once.
if(mInstance == null) mInstance = new Database();
// Returns the created object from a statically assigned field so its never
// destroyed until you do it manually.
return mInstance;
}
//Private constructor to stop you from creating this object by accident
private Database(){
//Init db object
}
}
So then from your fragments and activities you can then place the following field in your class's (Better use use a base activity and fragment to save you repeating code).
public abstract class BaseFragment extends Fragment {
protected final Database mDatabase = Database.getInstance();
}
Then your concrete fragments can extend your BaseFragment e.g. SearchListFragment extends BaseFragment
Hope this helps.
Worth reading about singletons and database
Regards,
Chris
Define an interface called Callbacks (or something else if you want). In it, have a public method called getClassX(). Then make your Activity implement the Callbacks interface.
In your Fragments, in onAttach, store a reference to a Callbacks object (i.e. your activity via something like:
if(activity instanceof Callbacks)
mCallbacks = (Callbacks)activity;
This will guarantee that the Fragments are able to call the function. (in case you want to reuse the fragments later in another app)
Then in your Activity, in onCreate(), create an instance of ClassX. In your getClassX() method, just return a reference to it.
When you want a reference to it from your Fragments, call mCallbacks.getClassX() and you should be sorted.
You can use a static object in your activity, and use it from the fragment, or call the getActivity() method in your fragment to access the whole activity objects/methods