I need to get the LifecycleOwner in an LifecycleObserver to pass it into an ViewModel observer.
This is my MainActivity, were I add the LifecycleObserver.
public class MainActivity extends AppCompatActivity implements LifecycleOwner{
private LifecycleRegistry mLifecycleRegistry;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow();
}
mLifecycleRegistry=new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
getLifecycle().addObserver(new MyLifecycleObserver());
}
#NonNull
#Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
And this is my observere, where I need the LifecycleOwner.
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(){
FirebaseMassage.startFirebase();
MainFragment.massageViewModel.getMassage().observe(/*here I need the LifecycleOwner*/, textMassage -> {
FirebaseMassage.updateFirebaseMassage(textMassage);
});
}
}
You can just use another signature to get the LifecycleOwner like:
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(LifecycleOwner owner){
...
}
}
Observer methods can receive zero or one argument. If used(means you can go with zero argument too but IFF arguments are used), the first argument must be of type LifecycleOwner. Methods annotated with Lifecycle.Event.ON_ANY can receive the second argument, which must be of type Lifecycle.Event.
class TestObserver implements LifecycleObserver {
#OnLifecycleEvent(ON_CREATE)
void onCreated(LifecycleOwner source) {
//one argument possible
}
#OnLifecycleEvent(ON_START)
void onCreated() {
//no argument possible
}
#OnLifecycleEvent(ON_ANY)
void onAny(LifecycleOwner source, Event event) {
//two argument possible only for ON_ANY event
}
}
You shouldn't need to implement your own LifecycleRegistry - just use the one available from AppCompatActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow();
}
getLifecycle().addObserver(new MyLifecycleObserver());
}
}
If you separate the startFirebase call and the viewmodel observer you can observe the changes from the viewmodel directly in the fragment, i.e.
MyLifecycleObserver starts the firebase call when ON_START is emitted.
public class MyLifecycleObserver implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStartListener(){
FirebaseMassage.startFirebase();
}
}
MainFragment observes the ViewModel directly.
public class MainFragment extends Fragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
massageViewModel.getMassage().observe(this, textMassage -> {
FirebaseMassage.updateFirebaseMassage(textMassage);
});
}
Since #OnLifecycleEvent is deprecated, I believe the best approach would be to implement LifecycleObserver and override lifecycle methods:
class TestObserver: LifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
// your code
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
// your code
}
}
The official documentation of Abhishek Kumar's answer comes from here:
https://developer.android.com/reference/androidx/lifecycle/Lifecycle#subclasses-direct:~:text=Observer%20methods%20can%20receive%20zero%20or%20one%20argument
Here is the document itself:
Observer methods can receive zero or one argument. If used, the first argument must be of type LifecycleOwner.
Methods annotated with Lifecycle.Event.ON_ANY can receive the second argument, which must be of type Lifecycle.Event.
class TestObserver implements LifecycleObserver {
#OnLifecycleEvent(ON_CREATE)
void onCreated(LifecycleOwner source) {}
#OnLifecycleEvent(ON_ANY)
void onAny(LifecycleOwner source, Event event) {}
}
Related
I want to load tasks in a fragment, in fragment's onViewCreated,I register the LiveData observer, in fragment's onResume,I load the task asynchronously, when first enter the fragment,it works fine,but when I navigate to other fragments then back to the task fragment, the callback onChanged() will be called twice.
I know If LiveData already has data set, it will be delivered to the observer, so when back to the task fragment, onChanged will be triggered when registering the observer in onViewCreated, and in onResume, will trigger onChanged the second time, I want to know how to avoid this. I have searched a lot, I know there is an EventWrapper, which can mark the content consumed when onChanged triggered the first time. but I think this approach is too heavy. Sorry for my poor English...
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle
savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//...
mainViewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
mainViewModel.increaseTaskList.observe(getViewLifecycleOwner(), new
Observer<List<Task>>() {
#Override
public void onChanged(#Nullable List<Task> tasks) {
Log.d("ZZZ","data changed,IncreaseTaskListAdapter setData");
adapter.setData(tasks);
}
});
}
#Override
public void onResume() {
super.onResume();
mainViewModel.loadIncreasePointTaskList();
}
I found a simple solution,check the livedata value before load
#Override
public void onResume() {
super.onResume();
if (mainViewModel.increaseTaskList.getValue()==null) {
Log.d("ZZZ","IncreaseFragment loadTaskAsync");
mainViewModel.loadIncreasePointTaskList();
}
}
You could use SingleLiveEvent which won't be triggered as long as the content hasn't changed.
This is recommended by Google, though.
My simple solution is that declare one boolean variable as isFisrtCalled = false, then change it true inside your callback as the first time
isFirstCalled = false;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle
savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//...
mainViewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
mainViewModel.increaseTaskList.observe(getViewLifecycleOwner(), new
Observer<List<Task>>() {
#Override
public void onChanged(#Nullable List<Task> tasks) {
if (!isFirstCalled) {
isFirstCalled = true;
return;
} // this will ensure, you will discard fisrt callback
Log.d("ZZZ","data changed,IncreaseTaskListAdapter setData");
adapter.setData(tasks);
}
});
}
#Override
public void onResume() {
super.onResume();
mainViewModel.loadIncreasePointTaskList();
}
My simple solution is that extends your MutableliveData class, add your custom observer methods that take mutable live data parameters and one extra parameter type of boolean, this boolean will help to bypass the first call back of observer, My solution will prevent you to manually handle the boolean every time for every observer,
public class CustomMutableLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean byPass = new AtomicBoolean(false);
private LifecycleOwner owner;
#NonNull
private Observer<? super T> observer;
public CustomMutableLiveData() {
byPass.set(false);
}
#MainThread
#Override
public void observe(#NonNull LifecycleOwner owner, #NonNull final Observer<? super T> observer) {
super.observe(owner, observer);
}
#MainThread
public void setValue(T value) {
super.setValue(value);
if (this.byPass.get()) {
observe(owner, observer);
this.byPass.set(false);
}
}
public void addObserver(#NonNull LifecycleOwner owner, #NonNull final Observer<? super T> observer) {
addObserver(owner, observer, false);
}
public void addObserver(#NonNull LifecycleOwner owner, #NonNull final Observer<? super T> observer, boolean byPassMode) {
this.owner = owner;
this.observer = observer;
byPass.set(byPassMode);
if (!byPass.get()) {
observe(this.owner, this.observer);
}
}
}
Add observer with custom observer method like:
mViewModel.name.addObserver(this, name -> {
mBinding.tvName.setGreetingTextText(name);
},true);
mViewModel.name.setValue("Zeeshan 1")
mViewModel.name.addObserver(this, name -> {
mBinding.tvName.setGreetingTextText(name);
},true); // true for byPass call back
mViewModel.name.setValue("Zeeshan 2")
Above sample code only prints 'Zeeshan 2'
I hope this will help you.
You can now use SingleLiveEvent instead of MutableData.
So change this:
private val _biometricAuthenticationStatus = MutableLiveData(BiometricAuthenticationStatus.WAITING)
Into this:
private val _biometricAuthenticationStatus = SingleLiveEvent<BiometricAuthenticationStatus>()
I have ViewModel
class MyViewModel extends BaseViewModel{
public void foo(){
// some code or return some boolean
}
}
View Class
class MyView extends View{
private MyViewModel myviewmodel;
public void bindTo(MyViewModel viewModel) {
this.viewModel = viewModel;
context = viewModel.getContext();
validateView();
requestLayout();
}
private validateView(){
//some code
}
}
this bind view method bind with adapter
I want to get call back in Myview class when ever i will validateView will call please suggest me how get call back from Viewmodel method to View in android.
it is best practice to use live data for communicating from viewmodel to your view.
class MyViewModel {
private MutableLiveData<Boolean> state = new MutableLiveData<Boolean>;
public LiveData<Boolean> getState() {
return state;
}
public void foo() {
//bool = value returned of your work
state.setValue(bool);
} }
class Myview extends View {
public void onCreate() {
viewmodel.getState().observe(this, observer); // 'this' is life cycle owner
}
final Observer<Boolean> observer = new Observer<Boolean>() {
#Override
public void onChanged(#Nullable final Boolean state) {
// do your work with returned value
}
}; }
for more details refer to this
Correct Me if i wrong
first you need to make interface class
public interface ViewModelCallback {
void returnCallBack(Boolean mBoolean);
}
then your View class implements that interface class & Override that method
class MyView extends View implements ViewModelCallback
#Override
public void returnCallBack(Boolean mBoolean) {
//here you will retrieve callback
// Do Something
}
Next you just pass a value from your view model
class MyViewModel {
private ViewModelCallback myViewCallBack;
public void foo() {
Boolean yourReturnValue = false;
myViewCallBack.returnCallBack(yourReturnValue);
}
}
I have question about generic types, subtypes and mismatching between those. I have specific structure classes and interface. I'll show you and please explain me why the type mismatch occurs.
Let's say I'm preparing my MVP framework and I have following interfaces and classes:
This is highest abstraction
interface Presenter<in V : AbstractView> {
fun attachView(view: V)
fun detachView()
fun onDestory() {
}
}
The abstract class contains specific methods and implementation of Presenter
abstract class AbstractPresenter<V : AbstractView> : Presenter<V>, LifecycleObserver {
private var viewReference: WeakReference<V?>? = null
protected abstract fun onAttached(view: V)
final override fun attachView(view: V) {
viewReference = WeakReference(view)
onAttached(view)
}
final override fun detachView() {
viewReference?.clear()
viewReference = null
onDetached()
}
protected open fun onDetached() {
}
}
Contract
interface DashboardContract {
interface View : AbstractView {
}
abstract class Presenter : AbstractPresenter<View>(){
}
}
and finally
class DashboardPresenter : DashboardContract.Presenter() {
override fun onAttached(view: DashboardContract.View) {
}
}
In terms of AbstractView it looks simpler. There is just interface AbstractView. In contract DashboardContract.View extends AbstractView interface and my DashboardActivity implement this DashboardContract.View interface.
class DashboardActivity : BaseActivity(), DashboardContract.View { ... }
So when I create DashboardPresenter as a property in my DashboardActivity and create method fun getPresenter() : Presenter<AbstractView> then I got Type mismatch error Why? isn't a subtype of Presenter<AbstractView>?
fun getPresenter() : AbstractPresenter<AbstractView> {
return dashboardPresenter // The type is DashboardPresenter
}
Let's take a looka at the Java code:
I'm watching the Java code from decompile Kotlin. I put it below. This is how the Presenter looks like:
public interface Presenter {
void attachView(#NotNull AbstractView var1);
void detachView();
void onDestory();
#Metadata(...)
public static final class DefaultImpls {
public static void onDestory(Presenter $this) {
}
}
}
I thought that If I use generic class in Kotlin I get the generic class in java too. I was wrong.
The AbstractPresenter gives:
public abstract class AbstractPresenter implements Presenter, LifecycleObserver {
private WeakReference viewReference;
protected abstract void onAttached(#NotNull AbstractView var1);
public final void attachView(#NotNull AbstractView view) {
Intrinsics.checkParameterIsNotNull(view, "view");
this.viewReference = new WeakReference(view);
this.onAttached(view);
}
public final void detachView() {
WeakReference var10000 = this.viewReference;
if(this.viewReference != null) {
var10000.clear();
}
this.viewReference = (WeakReference)null;
this.onDetached();
}
protected void onDetached() {
}
public void onDestory() {
DefaultImpls.onDestory(this);
}
}
Contract
public interface DashboardContract {
#Metadata(...)
public interface View extends AbstractView {
}
#Metadata(...)
public abstract static class Presenter extends AbstractPresenter {
}
}
The DashboardPresetner:
public final class DashboardPresenter extends Presenter {
protected void onAttached(#NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
}
// $FF: synthetic method
// $FF: bridge method
public void onAttached(AbstractView var1) {
this.onAttached((View)var1);
}
}
You have to change the parent of Presenter in DashboardContractto use AbstractView instead of View:
abstract class Presenter : AbstractPresenter<AbstractView>()
I'm not sure why you're not allowed to use View instead, this might be a flaw
in the recursive type checking of Kotlin. It might be interesting to see what the corresponding java code is and continue investigating from that.
Im using MVP and RxJava similar to google-samples repo.
And I would like to ask how to correctly handle screen orientation change.
There is another strategy that enables saving presenter state and also Observable's state: retain Fragment. This way you omit standard Android way of saving data into Bundle (which enables only to save simple variables and not the state of network requests.)
Activity:
public class MainActivity extends AppCompatActivity implements MainActivityView {
private static final String TAG_RETAIN_FRAGMENT = "retain_fragment";
MainActivityPresenter mPresenter;
private MainActivityRetainFragment mRetainFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
initRetainFragment();
initPresenter();
}
private void initRetainFragment() {
FragmentManager fm = getSupportFragmentManager();
mRetainFragment = (MainActivityRetainFragment) fm.findFragmentByTag(TAG_RETAIN_FRAGMENT);
if (mRetainFragment == null) {
mRetainFragment = new MainActivityRetainFragment();
fm.beginTransaction().add(mRetainFragment, TAG_RETAIN_FRAGMENT).commit();
}
}
private void initPresenter() {
mPresenter = mRetainFragment.getPresenter();
mRetainFragment.retainPresenter(null);
if (mPresenter == null) {
mPresenter = new MainActivityPresenter();
}
mPresenter.attachView(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (!isFinishing()) {
mRetainFragment.retainPresenter(mPresenter);
return;
}
mPresenter.detachView();
mPresenter = null;
}
}
Retain Fragment:
public class MainActivityRetainFragment extends Fragment {
private MainActivityPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void retainPresenter(MainActivityPresenter presenter) {
this.presenter = presenter;
}
public MainActivityPresenter getPresenter() {
return presenter;
}
}
Notice the way activity lifecycle events are handled. When the Activity is created, retain Fragment is added to the backstack and on lifecycle events it is restored from backstack. retain Fragment does not have any view, it is just a holder for presenter during configuration changes. Notice the main invocation that enables restoring exactly the same fragment (and it's content) from backstack:
setRetainInstance(true)
If you are concerned about memory leaks: every time the presenter is restored presenter's view is restored:
mPresenter.attachView(this);
So the previous Activity reference is replaced by new one.
More about such handling of configuration changes here here
I handled by encapsulating view's state in specific ViewState class in presenter, and it is easy to test.
public interface BaseViewState {
void saveState(#NonNull Bundle outState);
void restoreState(#Nullable Bundle savedInstanceState);
}
class HomeViewState implements BaseViewState {
static final long NONE_NUM = -1;
static final String STATE_COMIC_NUM = "state_comic_num";
private long comicNum = NONE_NUM;
#Inject
HomeViewState() {
}
#Override
public void saveState(#NonNull Bundle outState) {
outState.putLong(STATE_COMIC_NUM, comicNum);
}
#Override
public void restoreState(#Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
comicNum = savedInstanceState.getLong(STATE_COMIC_NUM, NONE_NUM);
}
}
long getComicNumber() {
return comicNum;
}
void setComicNum(long comicNum) {
this.comicNum = comicNum;
}
}
get/set values from viewState in presenter, this helps to keep it updated, as well as presenter stateless.
public class HomePresenter implements HomeContract.Presenter {
private HomeViewState viewState;
HomeViewState getViewState() {
return viewState;
}
#Override
public void loadComic() {
loadComic(viewState.getComicNumber());
}
...
}
in Activity as View should initiate call to save and restore.
public class MainActivity extends BaseActivity implements HomeContract.View {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
homePresenter.getViewState().restoreState(savedInstanceState);
}
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
homePresenter.getViewState().saveState(outState);
}
...
}
I can easily detect when Fragments are attached to Activity via Activity.onAttachFragment()
But how can I detect in Activity that some Fragment is detached from activity?
There is no Activity.onDetachFragment()
Is subcclasing Fragment and write some code to notify Activity about that state is the only solution?
you can use interface for communicating between Fragment and Activity
something like this :
public Class MyFragment extends Fragment {
FragmentCommunicator communicator;
public void setCommunicator(FragmentCommunicator communicator) {
this.communicator = communicator;
}
#Override
public void OnDetach() {
communicator.fragmentDetached();
}
...
public Interface FragmentCommunicator {
public void fragmentDetached();
}
}
and in your activity :
public Class MyActivity extends Activity Implements FragmentCommunicator {
...
MyFragment fragment = new MyFragment();
fragment.setCommunicator(this);
...
#Override
public void fragmentDetached() {
//Do what you want!
}
}
Edit:
the new approach is setting interface instance in onAttach.
public void onAttach(Activity activity) {
if (activity instanceof FragmentCommunicator) {
communicator = activity;
} else {
throw new RuntimeException("activity must implement FragmentCommunicator");
}
}
now there is no need to have setCommunicator method.
Mohammad's original answer is close to I would do. He has since updated it to leverage a mechanism provided by Android - Fragment.onAttach(Context context).In that approach, the fragment grabs components (ie, the activity) from the system and calls into it. This breaks inversion of control.
Here is my preferred approach:
public class MyActivity extends AppCompatActivity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof MyFragment) {
((MyFragment) fragment).setListener(mMyFragmentListener);
}
}
private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
#Override
public void onDetached(MyFragment fragment) {
fragment.setListener(null);
}
// implement other worker methods.
};
}
public class MyFragment extends Fragment {
#Nullable
private Listener mListener;
public void setListener(#Nullable Listener listener) {
mListener = listener;
}
public interface Listener {
void onDetached(MyFragment fragment);
// declare more worker methods here that leverage the connection.
}
#Override
public void onDetach() {
super.onDetach();
if (mListener != null) {
mListener.onDetached(this);
}
}
}
In this solution, the fragment doesn't dictate it's surroundings. Some control is given the to fragment in that it breaks the connection itself. We also already don't own the detaching of the fragment anyways, so clearing the listener is really just cleanup.
Here is an alternative approach that is more explicit, less prone to developer error, but also creates extra boiler plate (I prefer the previous approach because the goodbye handshake feels like an unnecessary distraction):
public static class MyActivity extends AppCompatActivity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof MyFragment) {
((MyFragment) fragment).setListener(mMyFragmentListener);
}
}
private final MyFragment.Listener mMyFragmentListener = new MyFragment.Listener() {
#Override
public void onDetached(MyFragment fragment) {
fragment.setListener(null);
}
// implement other worker methods.
};
}
public static class MyFragment extends Fragment {
#Nullable
private Listener mListener;
public void setListener(#Nullable Listener listener) {
mListener = listener;
}
public interface Listener {
void onDetached(MyFragment fragment);
// declare more worker methods here that leverage the connection.
}
#Override
public void onDetach() {
super.onDetach();
if (mListener != null) {
mListener.onDetached(this);
}
}
}
You have a callback in the fragment life cycle. onDetach() is called when fragment is no longer attached to activity.
An alternative would be:
mFragmentManager.findFragmentByTag("Tag").getView()
If the view is null the fragment must be detached.
You can use ViewModel for update host activity. Shared ViewModel could be better choice than across the old listener based polymorphism model. You can follow the official documentation.
Data, fragment lifecyle etc. can be observable with shared viewmodel.
sealed class FragmentStates {
object Attached : FragmentStates()
object Started : FragmentStates()
object Stopped : FragmentStates()
object DeAtached : FragmentStates()
}
class FragmentStateViewModel : ViewModel() {
private val _fragmentState = MutableLiveData<FragmentStates>()
val fragmentStates: LiveData<FragmentStates> get() = _fragmentState
fun fragmentAttached() {
_fragmentState.value = FragmentStates.Attached
}
fun fragmentDeAtached() {
_fragmentState.value = FragmentStates.DeAtached
}
}
class HostActivity : AppCompatActivity() {
private val fragmentStateViewModel: FragmentStateViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fragmentStateViewModel.fragmentStates.observe(this, Observer {
when(it) {
FragmentStates.Attached -> {}
FragmentStates.Started -> {}
FragmentStates.Stopped -> {}
FragmentStates.DeAtached -> {}
}
})
}
}
class MyFragment: Fragment() {
private val fragmentStateViewModel: FragmentStateViewModel by activityViewModels()
override fun onAttach(context: Context) {
super.onAttach(context)
fragmentStateViewModel.fragmentAttached()
}
override fun onDetach() {
super.onDetach()
fragmentStateViewModel.fragmentDeAtached()
}
}