For an ordinary (non-nested fragment) I use the following approach
1) create dependencies(...) method for setting fragment's dependencies
class MyFragment extends MyFragment {
void dependencies(Deps deps);
}
2) in MyFragment parent's activity onAttachFragment() method I just provide dependencies for fragment
class MyActivity{
void onAttachFragment(Fragment f){
((MyFragment)f).dependencies(deps);
}
}
For nested fragment there is no more onAttachFragment fragment called.
Providing dependencies for fragment just for providing dependencies for nested fragment seems to be very cumbersome. So how could I provide dependencies for it?
Just do it off the context which will be an activity. Create a getter for the dependencies on your activity. Fragments have access to the parent activity whether nested or not. Cast the context and then call the getter to get the dependencies in the nested activity.
If MyFragment depends upon MyNestedFragment, and MyNestedFragment depends upon Deps; it follows that MyFragment also depends upon Deps. Of course, no instance of MyNestedFragment exists when Activity.onAttachFragment() is called, so you will have to wait until after you have inflated the layout in MyFragment.onCreateView() before supplying MyNestedFragment with its dependencies.
public class MyActivity {
...
void onAttachFragment(Fragment f){
((MyFragment)f).dependencies(deps);
}
public static class MyFragment extends Fragment {
private Deps deps;
void dependencies(Deps deps) {
this.deps = deps;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// <fragment> element in fragment_main layout has
// android:tag set to nested_fragment
((MyNestedFragment)getChildFragmentManager()
.findFragmentByTag("nested_fragment"))
.dependencies(this.deps);
return rootView;
}
}
public static class MyNestedFragment extends Fragment {
void dependencies(Deps deps) {
...
}
}
...
}
If all of this seems a bit messy, that's because Fragments are not POJOs you can just wire up in some arbitrary manner. Their lifecycles must be managed by nested FragmentManagers. If you create your fragments programmatically rather than using the <fragment> element, you will have a bit more control over their lifecycle at the cost of more complexity.
If you want to treat Android like an IoC container, then RoboGuice may be what you are looking for:
public class MyActivity extends roboguice.activity.RoboFragmentActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
// This only needs to be called once for the whole app, so it could
// be in the onCreate() method of a custom Application subclass
RoboGuice.setUseAnnotationDatabases(false);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public static class MyNestedFragment extends Fragment {
#Inject
private Deps deps;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// this isn't necessary if you extend RoboFragment
roboguice.RoboGuice.getInjector(activity).injectMembers(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//This would not even be possible in the previous example
// because onCreateView() is called before dependencies()
// can be called.
deps.method();
View rootView = inflater.inflate(R.layout.fragment_nested, container, false);
return rootView;
}
}
}
#Singleton
public class Deps {
public void method() {
System.out.println("Deps.method()");
}
}
You try to set the dependecies, when the fragments are attached. Instead of this, try to get the dependencies from the fragment when needed. There is an example:
public class MyActivity extends Activity {
public Deps getDepsForFragment(Fragment fragment) {
if (fragment instanceof MyFragment) {
return depsForMyFragment;
} else if (fragment instanceof MyNestedFragment) {
return depsForMyNestedFragment;
} else {
return null;
}
}
}
public class MyFragment extends Fragment {
private Deps deps;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
MyActivtiy myActivity = (MyActivtiy) context;
deps = myActivity.getDepsForFragment(this);
} catch (ClassCastException e) {
throw new ClassCastException("This fragment attached to an activity which can't provide the required dependencies.");
}
}
}
// this is the same as the MyFragment
public class MyNestedFragment extends Fragment {
private Deps deps;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
MyActivtiy myActivity = (MyActivtiy) context;
deps = myActivity.getDepsForFragment(this);
} catch (ClassCastException e) {
throw new ClassCastException("This fragment attached to an activity which can't provide the required dependencies.");
}
}
}
Of course, you can make separated method for get deps in the activity (like getDepsForMyFragment and getDepsForMyNestedFragment).
Just keep the hierarchy logics, and it should be something like this:
class MyActivity{
void onAttachFragment(Fragment f){
((MyFragment)f).dependencies(deps);
}
}
class MyFragment extends MyFragment {
void dependencies(Deps deps) {
//TODO: do dependencies of my fragment before
((MyNestedFragment)childF).nestedDependencies(deps);
//TODO: do dependencies of my fragment after
}
}
class MyNestedFragment extends MyNestedFragment {
void nestedDependencies(Deps deps);
}
Related
I have a button in my fragment which opens a BottomSheetDialogFragment. I want to notify the host fragment if the user selected an item on the BottomSheetDialogFragment. In order to achieve this, I have made an interface in my BottomSheetDialogFragment. However, that interface only communicates with the host activity, not the fragment. How can I send the information from the dialog to the fragment?
This is my interface:
public interface BottomSheetListener {
void onButtonClicked(int index);
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
getParentFragment will return the parent fragment, if the current fragment is attached to a fragment else it will return null if it is attached directly to an Activity
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
When you use a lot of fragments, nested fragments or dialogfragments it becomes messy for communicate between them. I am suggesting to use ViewModel with LiveData for passing and updating data.
first add this to build gradle :
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
then create ViewModel class :
public class YourViewModel extends AndroidViewModel {
private MutableLiveData<Integer> yourMutableLiveData=new MutableLiveData<>();
public YourViewModel(#NonNull Application application) {
super(application);
}
public MutableLiveData<Integer> getYourMutableLiveData() {
return yourMutableLiveData;
}
}
This the fragment you want set value :
public class FragmentA extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().setValue(0);
}
}
And this is the fragment you want to get value when updated :
public class FragmentB extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
}
});
}
}
It can work on dialog fragment as well as I tested.
Notes :
-Do not pass context or any view into view model.
-Remember that onActivityCreated comes after onCreateView.
-Do not set this key to
YourViewModel yourViewModel =new ViewModelProvider(this).get(YourViewModel.class);
in fragment if you want to pass data fragment to fragment but you can pass in activity.
-You can set more than one observer to the data.
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)
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");
Let's consider a case where I have Fragment A and Fragment B.
B declares:
public interface MyInterface {
public void onTrigger(int position);
}
A implements this interface.
When pushing Fragment B into stack, how should I pass reference of Fragment A for it in Bundle so A can get the onTrigger callback when needed.
My use case scenario is that A has ListView with items and B has ViewPager with items. Both contain same items and when user goes from B -> A before popping B it should trigger the callback for A to update it's ListView position to match with B pager position.
Thanks.
Passing interface to Fragment
I think you are communicating between two Fragment
In order to do so, you can have a look into Communicating with Other Fragments
public class FragmentB extends Fragment{
MyInterface mCallback;
// Container Activity must implement this interface
public interface MyInterface {
public void onTrigger();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (MyInterface ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MyInterface ");
}
}
...
}
For Kotlin 1.0.0-beta-3595
interface SomeCallback {}
class SomeFragment() : Fragment(){
var callback : SomeCallback? = null //some might want late init, but I think this way is safer
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
callback = activity as? SomeCallback //returns null if not type 'SomeCallback'
return inflater!!.inflate(R.layout.frag_some_view, container, false);
}
}
It is optimal for two fragments to only communicate through an activity. So you can define an interface in Fragment B that is implemented in the activity. Then in the activity, define in the interface method what you want to happen in fragment A.
In Fragment B,
MyInterface mCallback;
public interface MyInterface {
void onTrigger(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (MyInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MyInterface");
}
}
Method for determining if user goes from B to A
public void onChangeFragment(int position){
//other logic here
mCallback.onTrigger(position);
}
In Activity,
public void onTrigger(int position) {
//Find listview in fragment A
listView.smoothScrollToPosition(position);
}
Goodluck!
Using #Amit's answer, and adapting to the OPs question, here is all the relevant code:
public class FragmentA extends BaseFragment implements MyInterface {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// THIS IS JUST AN EXAMPLE OF WHERE YOU MIGHT CREATE FragmentB
FragmentB myFragmentB = new FragmentB();
}
void onTrigger(int position){
// My Callback Happens Here!
}
}
...
public class FragmentB extends BaseFragment {
private MyInterface callback;
public interface MyInterface {
void onTrigger(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
callback = (MyInterface ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement MyInterface");
}
}
}
I think you should use communication, as I've written below. This code comes from this Android Dev page of communication between Fragments:
HeadlinesFragment
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
public void setOnHeadlineSelectedListener(Activity activity) {
mCallback = activity;
}
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
// ...
}
MainActivity
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
// ...
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof HeadlinesFragment) {
HeadlinesFragment headlinesFragment = (HeadlinesFragment) fragment;
headlinesFragment.setOnHeadlineSelectedListener(this);
}
}
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener {
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
You may create call back interface by this way.
var screenVisibility=activity as YourActivity
screenVisibility.setScreenVisibility("which screen you want")
Sorry for my English
I started to learn how to use TDD in Android Development.
I need to test a Fragment in isolation. I used this tutorial, but I got exception when I tried to commit transaction (to add fragment in activity) in test. I found solution - using transaction.commitAllowingStateLoss() instead of transaction.commit(). But I'm not sure that this solution will always work correctly.
So, now I use something like this:
in application package (not test package) I have package helper_to_test_fragments with next classes:
FragmentBuilder.java
public interface FragmentBuilder {
Fragment build();
}
CurrentTestedFragmentBuilder.java
public class CurrentTestedFragmentBuilder {
private static class DummyFragmentBuilder implements FragmentBuilder {
#Override
public Fragment build() {
Fragment dummyFragment = new Fragment() {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// inflate empty FrameLayout
return inflater.inflate(R.layout.fragment_dummy, container, false);
}
};
return dummyFragment;
}
}
private static FragmentBuilder currentFragmentBuilder = new DummyFragmentBuilder();
public static void setCurrentBuilder(FragmentBuilder fragmentBuilder) {
if (fragmentBuilder == null) {
throw new IllegalArgumentException("fragmentBuilder should be not null");
}
currentFragmentBuilder = fragmentBuilder;
}
public static Fragment build() {
return currentFragmentBuilder.build();
}
}
ActivityHelperToTestFragments.java
// This activity declared in application Manifest.xml file
public class ActivityHelperToTestFragments extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_helper_to_test_fragments);
if (savedInstanceState == null) {
Fragment currentTestedFragment = CurrentTestedFragmentBuilder.build();
getFragmentManager()
.beginTransaction()
.add(R.id.containerOfCurrentTestedFragment, currentTestedFragment)
.commit();
}
}
public Fragment getTestedFragment() {
return getFragmentManager().findFragmentById(R.id.containerOfCurrentTestedFragment);
}
}
activity_helper_to_test_fragments.xml - layout for helper activity above
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/containerOfCurrentTestedFragment" />
Using:
public class TestSomeFragment
extends ActivityInstrumentationTestCase2<ActivityHelperToTestFragments> {
private ActivityHelperToTestFragments activity;
private Fragment testedFragment;
public TestSomeFragment() {
super(ActivityHelperToTestFragments.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
CurrentTestedFragmentBuilder.setCurrentBuilder(new FragmentBuilder() {
#Override
public Fragment build() {
return new SomeFragment();
}
});
activity = getActivity();
getInstrumentation().waitForIdleSync();
testedFragment = activity.getTestedFragment();
}
public void testPreconditions() {
assertNotNull(activity);
}
public void testFragment() {
assertNotNull(testedFragment);
}
}
What are the drawbacks to this approach? Is this approach correct? How easy it is, in your opinion?
I use static methods for setting the current tested fragment. But I use this solution for testing fragments in isolation.
Please, suggest solution that is better than this.