BroadcastReceiver in order to pass data from another Activity to Fragment - android

Let's say I have MainActivity where are few Fragments in ViewPager. I want to pass data from another Activity to one of these fragments. I'm doing this by BroadcastReceiver.
public class MyFragment extends Fragment {
private MyFragmentReceiver mReceiver;
public MyFragment() {
super();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReceiver = new MyFragmentReceiver();
getActivity().registerReceiver(mReceiver, new IntentFilter("fragmentUpdater"));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_my, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// My code here
}
public class MyFragmentReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//My methods
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (mReceiver != null)
getActivity().unregisterReceiver(mReceiver);
}
}
So in my AnotherActivity I'm doing something like this:
Intent data = new Intent("fragmentUpdater");
MyApplication.getInstance().getMainActivity().sendBroadcast(data);
Where MyApplication is singleton which contains MainActivity.
I noticed that BroadcastReceiver is putting something into logs, and I am wondering is that the best way to do it.
Are there better ways to pass data from another activity to specific Fragment or call methods in that Fragment?
Do I have to include something in AndroidManifest.xml related to BroadcastReceiver?

One alternative is using an interface for communicating between your activity and fragments. Example:
Interface
public interface MyInterface {
void setSomeValue(int someValue);
int getSomeValue();
}
Activity
public class MyActivity extends Activity implements MyInterface {
private int someValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do the usual stuff
}
// implement from MyInterface
#Override
public void setSomeValue(int someValue) {
this.someValue = someValue;
}
// implement from MyInterface
#Override
public int getSomeValue() {
return someValue;
}
}
Fragment
public class MyFragment extends Fragment {
private MyInterface mi;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mi = (MyInterface) context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mi.setSomeValue(20);
int someValue = mi.getSomeValue();
}
}
You can use the interface to communicate between one or more activities, multiple fragments, views, tasks, services, etc etc etc. If you were to go this route, I would create a base activity which implements MyInterface and its methods, and have all other activities extend the base activity. I would even create a base fragment which calls onAttach(), and have all my other fragments extend this base fragment (so that I don't need to call onAttach() in every fragment).
UPDATE...
A base fragment would simply look like this:
public class BaseFragment extends Fragment {
public MyInterface mi;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mi = (MyInterface) context;
}
}
Now, MyFragment would just extend BaseFragment...
public class MyFragment extends BaseFragment {
...
}
There's no need now to attach or even declare MyInterface in any fragment extending BaseFragment, the base fragment already has a public instance of it. You just set/get/etc via your interface without any additional fuss:
mi.setSomeValue(20);

I would use LocalBroadcastManager instead, it gives you the following advantages :
You know that the data you are broadcasting won't leave your app, so
don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts
to your app, so you don't need to worry about having security holes
they can exploit.
It is more efficient than sending a global broadcast through the system.
This is directly from the official docs

You may pass the data using Extras.
Intent data = new Intent("fragmentUpdater");
data.putExtra("STRING_YOU_NEED", strName);
and you can get the data inside onReceive function by :
String data_needed_here= extras.getString("STRING_YOU_NEED");

Related

how to call method from activity in another class

i need to get method from appcompatactivity to this class and call this method in another appcaompatactity like this
public class WareHouseActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_warehouse);
}
public void showToast(){
Toast.makeText(WareHouseActivity.this,"warehouse",Toast.LENGTH_SHORT).show();
}
}
call method showToast from appcampatactivity in this class :
public class Common {
public static void showToast(Activity activity){
((WareHouseActivity)activity).showToast();
}
}
and i try with context instead of using Activity like:
public class Common {
public static void showToast(Context context){
((WareHouseActivity)context).showToast();
}
}
call method showToast from class in another appcompatactivity :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_warehouse);
Common.showToast(MainActity.this);
}
}
If you want to share a method with multiple Activities, it cannot live on an Activity instance. You can't be guaranteed that the instance exists when another Activity is being shown, and you should never create Activity instances yourself.
If you move the full method to a separate class like this:
public class Common {
// You must pass in any arguments needed in the function
public static void showToast(Context context){
Toast.makeText(context,"warehouse",Toast.LENGTH_SHORT).show();
}
}
Then you can call it from any activity
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_warehouse);
Common.showToast(this);
}
Update
If the reason you want to do this is to have shared data that you can access from both activities, that data should not live in one of the activities. Have a look at the activity lifecycle, activities will be destroyed when the device is rotated, or can be destroyed when in the background by the OS. Any temporary data you store on an activity would be lost when that happens.
One simple option for storing some temporary data is a singleton class. This is not persistent - the data will still be lost if your app is stopped and restarted). If you need persistent data you should use SharedPreferences or a database for that. However, it will let some temporary data live longer than an individual activity's lifecycle and be accessible from multiple activities or fragments.
class Common {
private static Common instance = null;
private Common() {}
public static synchronized Common getInstance() {
if (instance == null) {
instance = new Common();
}
return instance;
}
final List<String> names = new ArrayList<>();
String message = "";
void showMessage(Context ctx) {
Toast.makeText(ctx,message,Toast.LENGTH_SHORT).show();
}
}
Then you can set and use data stored in this class from multiple activities, like this
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Common c = Common.getInstance();
c.message = "Hello from Main";
c.names.add("Test");
}
}
public class SecondActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Common c = Common.getInstance();
c.showMessage(this)
c.names.add("Test Two");
}
}

Extend BottomSheetDialogFragment from a class instantiated in a fragment in Android

I have a fragment (FragmentSearchResults) that contains results retrieved from a database, in which there is a button "filters". When the user taps on such a button, a class (FiltersDialog) extending a BottomSheetDialogFragment is instantiated, so that the user can set his filters. When the user closes the FiltersDialog activity, the values are passed from FiltersDialog to FragmentSearchResults.
public class FragmentSearchResults extends Fragment implements FiltersDialog.FilterListener {
/* code */
ImageButton btnFilter = myFragment.findViewById(R.id.btn_filters);
btnFilter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showFilters();
}
});
}
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onFiltersSet(Map filters) {
// apply filters selected by user
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
}
public class FiltersDialog extends BottomSheetDialogFragment {
private FilterListener mListener;
private Map<String, Object> mFilters;
public FiltersDialog() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_filters_dialog, container, false);
TextView txtSave = v.findViewById(R.id.txt_save_filters);
mTxtSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onFiltersSet(mFilters);
}
});
return v;
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
if (context instanceof FilterListener) {
mListener = (FilterListener) context;
}
else {
// Here's the error, as the activity Home.java containing FragmentSearchResults
// does not implement FilterListener, FragmentSearchResults does
throw new RuntimeException(context.toString() + " must implement FilterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The problem is that FilterListener needs to be implemented in FragmentSearchResults, but I am passing the activity Home context.
How can I implement FilterListener in the fragment?
Why don't you create method inside your FiltersDialog, like
public void setFiltersListener(FiltersDialog.FilterListener listener) {
mListener = listener;
}
and simply call it after you instantiate the dialog.
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setFiltersListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
Then you can use the listener inside dialog. something like this
if (mListener != null) {
mListener.onFiltersSet(mFilters);
}
How can I setup listener to the dialog?
parameter of onAttach in Fragment is FragmentHost(Activity). thus, it can't typecast to FilterListener.
I suggest a simple way to implement FilterListener setter in FragmentDialog as below code.
... in FiltersDialog
public void setListener(FilterListener listener) {
mListener = listener;
}
...
... in FragmentSearchResults
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
...
//When FragmentSearchResults recreated, FiltersDialog must also need to be recreated.
A better approach will be to use LiveData, ViewModel in this case. Use Shared ViewModel Approach, An Activity Level ViewModel can be accessed via all the fragments lying in its environment.
Make an Activity Level ViewModel
Define a LiveData in ViewModel
When your "FragmentSearchResults" opens for the first time, start
observing it.
When You open "FiltersDialog" screen and click save button, Then post
to LiveData changes in the filter (You have activity context here,
You can fetch ActivityViewModel here, get LiveData from it, post
changes to this LiveData)
Now As "FragmentSearchResults" is already observing changes in the
LiveData, You will get callback here, make changes accordingly. This way your code will be completely decoupled. You will be escaped from
hustles of Interfaces.

Is it fine to create a separate interface for call back function in MVP pattern

I am trying to create an app by using MVP design pattern. This is the first time i am using this pattern, thats the reason i am little concerned that either i am following the pattern correctly or not.
This is what i have done so far. I am not using Dagger2.
Interface
public interface MainActivityMVP {
interface Model{
void sendTokenToServer(MainActivityMVP.Presenter presenter);
}
interface View{
boolean isPnTokenRegistered();
void tokenUpdated();
void tokenFailedToUpdate();
}
interface Presenter{
void tokenUpdatedSuccessfully();
void tokenAlreadyExists();
void detachView();
}
On MainActivity, I have created an instance of Presenter and Model and pass the Model object to Presenter Constructor
MainActivity
public class MainActivity extends BaseActivity implements MainActivityMVP.View {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
mainPresenter= new MainPresenter(this, new MainModel());
mainPresenter.sendFCMTokenToServer();
}
On Presenter I call Model's method to perform operation, and pass presenter reference to it.
Presenter
public class MainPresenter implements MainActivityMVP.Presenter{
MainActivityMVP.View view;
MainActivityMVP.Model model;
public MainPresenter(MainActivityMVP.View view, MainActivityMVP.Model model){
this.view= view;
this.model= model;
}
public void sendFCMTokenToServer() {
model.sendTokenToServer(this);
}
#Override
public void tokenUpdatedSuccessfully() {
view.tokenUpdated();
}
#Override
public void tokenAlreadyExists() {
view.tokenFailedToUpdate();
}
In Model, I create instance of PreferenceManager class that gets data from SharedPreference
public class MainModel implements MainActivityMVP.Model {
PreferencesHelper preferencesHelper;
public MainModel(){
preferencesHelper= new PreferencesHelper();
}
#Override
public void sendTokenToServer(MainActivityMVP.Presenter presenter) {
if (preferencesHelper.getNotificationSettings().isEmpty()) {
//do stuff
presenter.tokenUpdatedSuccessfully();
}
}
Now i have these questions.
Is the above approach of implementing MVP pattern is fine, or i am
missing something here.
Is it fine if i add an other interface for call backs, or passing
Presenter to model is better approach, as i have seen some example
where they pass interactor reference to model.
Is it necessary to create Interactor Class in MVP pattern
Is it fine, and not against MVP rule if i create a separate
interface for Repository,
Developers have different varieties of implementing MVP. Few people use interactors. Its is not compulsory to use interactors in MVP. I will suggest you below since you are in a starting stage.
public interface MainView extends BaseView {
boolean isPnTokenRegistered();
void tokenUpdated();
void tokenFailedToUpdate();
}
Then have your basepresenter be like this
public interface BasePresenter<V extends BaseView> {
void setView(V view);
void destroyView();
void destroy();
}
Now your MainPresenter
public class MainPresenter implements BasePresenter<MainView>{
MainView view;
PreferencesHelper preferencesHelper;
MainPresenter(){
preferencesHelper= new PreferencesHelper();
}
#Override
public void setView(MainView view) {
this.view = view;
}
#Override
public void destroyView() {
this.view = null;
}
#Override
public void destroy() {
}
public void sendFCMTokenToServer() {
//Do whatever you want
}
}
Finally have your activity like this,
public class MainActivity extends BaseActivity implements MainView {
MainPresenter mainPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
mainPresenter= new MainPresenter();
mainPresenter.attachView(this)
mainPresenter.sendFCMTokenToServer();
}

Sharing data between fragments using new architecture component ViewModel

On Last Google IO, Google released a preview of some new arch components, one of which, ViewModel.
In the docs google shows one of the possible uses for this component:
It is very common that two or more fragments in an activity need to
communicate with each other. This is never trivial as both fragments
need to define some interface description, and the owner activity must
bind the two together. Moreover, both fragments must handle the case
where the other fragment is not yet created or not visible.
This common pain point can be addressed by using ViewModel objects.
Imagine a common case of master-detail fragments, where we have a
fragment in which the user selects an item from a list and another
fragment that displays the contents of the selected item.
These fragments can share a ViewModel using their activity scope to
handle this communication.
And shows a implementation example:
public class SharedViewModel extends ViewModel {
private final SavedStateHandle state;
public SharedViewModel(SavedStateHandle state) {
this.state = state;
}
private final MutableLiveData<Item> selected = state.getLiveData("selected");
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
#Override
protected void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
#Override
protected void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
I was quite excited about the possibility of not needing those interfaces used for fragments to communicate through the activity.
But Google's example does not show exactly how would I call the detail fragment from master.
I'd still have to use an interface that will be implemented by the activity, which will call fragmentManager.replace(...), or there is another way to do that using the new architecture?
Updated on 6/12/2017,
Android Official provide a simple, precise example to example how the ViewModel works on Master-Detail template, you should take a look on it first.Share data between fragments
As #CommonWare, #Quang Nguyen methioned, it is not the purpose for Yigit to make the call from master to detail but be better to use the Middle man pattern. But if you want to make some fragment transaction, it should be done in the activity. At that moment, the ViewModel class should be as static class in Activity and may contain some Ugly Callback to call back the activity to make the fragment transaction.
I have tried to implement this and make a simple project about this. You can take a look it. Most of the code is referenced from Google IO 2017, also the structure.
https://github.com/charlesng/SampleAppArch
I do not use Master Detail Fragment to implement the component, but the old one ( communication between fragment in ViewPager.) The logic should be the same.
But I found something is important using these components
What you want to send and receive in the Middle man, they should be sent and received in View Model only
The modification seems not too much in the fragment class. Since it only change the implementation from "Interface callback" to "Listening and responding ViewModel"
View Model initialize seems important and likely to be called in the activity.
Using the MutableLiveData to make the source synchronized in activity only.
1.Pager Activity
public class PagerActivity extends AppCompatActivity {
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private ViewPager mPager;
private PagerAgentViewModel pagerAgentViewModel;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pager);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
pagerAgentViewModel = new ViewModelProvider(this).get(PagerAgentViewModel.class);
pagerAgentViewModel.init();
}
/**
* A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
* sequence.
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
...Pager Implementation
}
}
2.PagerAgentViewModel (It deserved a better name rather than this)
public class PagerAgentViewModel extends ViewModel {
private final SavedStateHandle state;
private final MutableLiveData<String> messageContainerA;
private final MutableLiveData<String> messageContainerB;
public PagerAgentViewModel(SavedStateHandle state) {
this.state = state;
messageContainerA = state.getLiveData("Default Message");
messageContainerB = state.getLiveData("Default Message");
}
public void sendMessageToB(String msg)
{
messageContainerB.setValue(msg);
}
public void sendMessageToA(String msg)
{
messageContainerA.setValue(msg);
}
public LiveData<String> getMessageContainerA() {
return messageContainerA;
}
public LiveData<String> getMessageContainerB() {
return messageContainerB;
}
}
3.BlankFragmentA
public class BlankFragmentA extends Fragment {
private PagerAgentViewModel viewModel;
public BlankFragmentA() {
// Required empty public constructor
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);
textView = (TextView) view.findViewById(R.id.fragment_textA);
// set the onclick listener
Button button = (Button) view.findViewById(R.id.btnA);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewModel.sendMessageToB("Hello B");
}
});
//setup the listener for the fragment A
viewModel.getMessageContainerA().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(#Nullable String msg) {
textView.setText(msg);
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_a, container, false);
return view;
}
}
4.BlankFragmentB
public class BlankFragmentB extends Fragment {
public BlankFragmentB() {
// Required empty public constructor
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);
textView = (TextView) view.findViewById(R.id.fragment_textB);
//set the on click listener
Button button = (Button) view.findViewById(R.id.btnB);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewModel.sendMessageToA("Hello A");
}
});
//setup the listener for the fragment B
viewModel.getMessageContainerB().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(#Nullable String msg) {
textView.setText(msg);
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_b, container, false);
return view;
}
}
As written in the official Google tutorial now you may obtain a shared view model with by activityViewModels()
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
I have found a similar solution as others according to google codelabs example.
I have two fragments where one of them wait for an object change in the other and continues its process with updated object.
for this approach you will need a ViewModel class as below:
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import yourPackage.YourObjectModel;
public class SharedViewModel extends ViewModel {
public MutableLiveData<YourObjectModel> item = new MutableLiveData<>();
public YourObjectModel getItem() {
return item.getValue();
}
public void setItem(YourObjectModel item) {
this.item.setValue(item);
}
}
and the listener fragment should look like this:
public class ListenerFragment extends Fragment{
private SharedViewModel model;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.item.observe(getActivity(), new Observer<YourObjectModel>(){
#Override
public void onChanged(#Nullable YourObjectModel updatedObject) {
Log.i(TAG, "onChanged: recieved freshObject");
if (updatedObject != null) {
// Do what you want with your updated object here.
}
}
});
}
}
finally, the updater fragment can be like this:
public class UpdaterFragment extends DialogFragment{
private SharedViewModel model;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
}
// Call this method where it is necessary
private void updateViewModel(YourObjectModel yourItem){
model.setItem(yourItem);
}
}
It is good to mention that the updater fragment can be any form of fragments(not DialogFragment only) and for using these architecture components you should have these lines of code in your app build.gradle file. source
dependencies {
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
Before you are using a callback which attaches to Activity which is considered as a container.
That callback is a middle man between two Fragments.
The bad things about this previous solution are:
Activity has to carry the callback, it means a lot of work for
Activity.
Two Fragments are coupled tightly, it is difficult to update or change logic later.
With the new ViewModel (with support of LiveData), you have an elegant solution. It now plays a role of middle man which you can attach its lifecycle to Activity.
Logic and data between two Fragments now lay out in ViewModel.
Two Fragment gets data/state from ViewModel, so they do not need to know each other.
Besides, with the power of LiveData, you can change detail Fragment based on changes of master Fragment in reactive approach instead of previous callback way.
You now completely get rid of callback which tightly couples to both Activity and related Fragments.
I highly recommend you through Google's code lab. In step 5, you can find an nice example about this.
I implemented something similar to what you want, my viewmodel contains LiveData object that contains Enum state, and when you want to change the fragment from master to details (or in reverse) you call ViewModel functions that changing the livedata value, and activity know to change the fragment because it is observing livedata object.
TestViewModel:
public class TestViewModel extends ViewModel {
private MutableLiveData<Enums.state> mState;
public TestViewModel() {
mState=new MutableLiveData<>();
mState.setValue(Enums.state.Master);
}
public void onDetail() {
mState.setValue(Enums.state.Detail);
}
public void onMaster() {
mState.setValue(Enums.state.Master);
}
public LiveData<Enums.state> getState() {
return mState;
}
}
Enums:
public class Enums {
public enum state {
Master,
Detail
}
}
TestActivity:
public class TestActivity extends LifecycleActivity {
private ActivityTestBinding mBinding;
private TestViewModel mViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding=DataBindingUtil.setContentView(this, R.layout.activity_test);
mViewModel=ViewModelProviders.of(this).get(TestViewModel.class);
mViewModel.getState().observe(this, new Observer<Enums.state>() {
#Override
public void onChanged(#Nullable Enums.state state) {
switch(state) {
case Master:
setMasterFragment();
break;
case Detail:
setDetailFragment();
break;
}
}
});
}
private void setMasterFragment() {
MasterFragment masterFragment=MasterFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, masterFragment,"MasterTag").commit();
}
private void setDetailFragment() {
DetailFragment detailFragment=DetailFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, detailFragment,"DetailTag").commit();
}
#Override
public void onBackPressed() {
switch(mViewModel.getState().getValue()) {
case Master:
super.onBackPressed();
break;
case Detail:
mViewModel.onMaster();
break;
}
}
}
MasterFragment:
public class MasterFragment extends Fragment {
private FragmentMasterBinding mBinding;
public static MasterFragment newInstance() {
MasterFragment fragment=new MasterFragment();
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_master, container, false);
mBinding.btnDetail.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onDetail();
}
});
return mBinding.getRoot();
}
}
DetailFragment:
public class DetailFragment extends Fragment {
private FragmentDetailBinding mBinding;
public static DetailFragment newInstance() {
DetailFragment fragment=new DetailFragment();
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_detail, container, false);
mBinding.btnMaster.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onMaster();
}
});
return mBinding.getRoot();
}
}
I end up using the own ViewModel to hold up the listener that will trigger the Activity method. Similar to the old way but as I said, passing the listener to ViewModel instead of the fragment. So my ViewModel looked like this:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
private OnSelectListener<T> listener = item -> {};
public interface OnSelectListener <T> {
void selected (T item);
}
public void setListener(OnSelectListener<T> listener) {
this.listener = listener;
}
public void select(T item) {
selected.setValue(item);
listener.selected(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
in StepMasterActivity I get the ViewModel and set it as a listener:
StepMasterActivity.class:
SharedViewModel stepViewModel = ViewModelProviders.of(this).get("step", SharedViewModel.class);
stepViewModel.setListener(this);
...
#Override
public void selected(Step item) {
Log.d(TAG, "selected: "+item);
}
...
In the fragment I just retrieve the ViewModel
stepViewModel = ViewModelProviders.of(getActivity()).get("step", SharedViewModel.class);
and call:
stepViewModel.select(step);
I tested it superficially and it worked. As I go about implementing the other features related to this, I will be aware of any problems that may occur.
For those using Kotlin out there try the following approach:
Add the androidx ViewModel and LiveData libraries to your gradle file
Call your viewmodel inside the fragment like this:
class MainFragment : Fragment() {
private lateinit var viewModel: ViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// kotlin does not have a getActivity() built in method instead we use activity, which is null-safe
activity?.let {
viemModel = ViewModelProvider(it).get(SharedViewModel::class.java)
}
}
}
The above method is a good practice since it will avoid crashes due to null pointer exceptions
Edit: As btraas complemented: activity is compiled into getActivity() which is marked as #Nullable in the android SDK. activity and getActivity() are both accessible and equivalent.
You can set values from Detail Fragment to Master Fragment like this
model.selected.setValue(item)

How to callback an Activity in onAttach with Otto?

I am working on an app which uses the NavigationDrawer. Different fragments are placed into the content view of the MainActivity whenever a menu item in the drawer is selected.
To inform the MainActivity that a Fragment successfully attached the following callback is executed:
public class CustomFragment extends Fragment {
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).onSectionAttached();
}
}
Since I am started using Otto with Dagger in the project I am curious how I can substitute the callback with a .post() event such as:
mBus.post(new CustomFragmentAttachedEvent);
The problem is that mBus is null in onAttach(). It gets initialized in onCreate().
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApp) getActivity().getApplication()).getObjectGraph().inject(this);
}
Here is an example of such a Fragment class.
References:
Complete Android Fragment & Activity Lifecycle
You can easily try out the example yourself: Create a new project from the NavigationDrawer template available in Android Studio, add Dagger and Otto and try to substitute the mentioned callback.
Working solution:
Here is the example Fragment in the final working version.
You can create a provider for your Otto bus with Dagger as follows:
#Module(injects = {
YourFragment.class,
MainActivity.class
},complete = true)
public class EventBusModule {
#Provides
#Singleton
Bus provideBus() {
return new Bus();
}
}
Then you register the EventBusModule when you create your ObjectGraph. You can create your graph in the Application's onCreate():
public class MyApplication extends Application{
public void onCreate() {
Object[] modules = new Object[]{new EventBusModule()};
Injector.init(modules);
}
}
You would need to create an Injector that has some static methods and a reference to the ObjectGraph so you can manipulate it without having a reference to the Application. Something like this:
public final class Injector {
private static ObjectGraph objectGraph = null;
public static void init(final Object... modules) {
if (objectGraph == null) {
objectGraph = ObjectGraph.create(modules);
}
else {
objectGraph = objectGraph.plus(modules);
}
// Inject statics
objectGraph.injectStatics();
}
public static final void inject(final Object target) {
objectGraph.inject(target);
}
public static <T> T resolve(Class<T> type) {
return objectGraph.get(type);
}
}
Then you just use #Inject in your Fragment to let Dagger give you a Bus instance and inject the Fragment:
public class MyFragment extends Fragment{
#Inject Bus mBus;
public void onAttach(){
Injector.inject(this);
}
}

Categories

Resources