I need to fill a Custom Object with data obtained from differents fragments. One option is to do a parcelable object and pass it throught each fragment, but I think that is a better option to declare the object in the activity and access to it from each fragment without move data.
What it is the best option for this?
You can use an interface to communicate between your fragments and activities.
Android documentation explanation is pretty straight forward and with clear examples for what you need, check it here Communicating with Other Fragments
Basically you need to create an interface, declare it in your fragment and your activity should implement it. When you have the data in your fragment you can trigger that event in your activity passing the data and in your activity you will have the data to fill your custom object.
You can store data in a data Fragment, which uses setRetainInstance(true). setRetainInstance controls whether a fragment instance is retained across Activity re-creation (such as from a configuration change).
In Activity class, instantiate the data fragment. It can then be shared across multiple fragments of same activity.
Example data Fragment class:
public class MyDataFragment extends Fragment {
public static MyDataFragment newInstance() {
return new MyDataFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this instance so it isn't destroyed when MainActivity and MainFragment change configuration.
setRetainInstance(true);
}
// data shared across multiple fragments of same activity
public String myData = null;
}
Example Activity class:
public class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// common data fragment shared by multiple fragments
final FragmentManager fm = getSupportFragmentManager();
MyDataFragment myDataFragment = (MyDataFragment)fm.findFragmentByTag(MY_DATA_FRAGMENT);
if (myDataFragment == null) {
MyDataFragment dataFragment = MyDataFragment.newInstance();
fm.beginTransaction().add(dataFragment, MY_DATA_FRAGMENT).commit();
}
}
public static final String MY_DATA_FRAGMENT = "MY_DATA_FRAGMENT";
In various fragments, you can access the data fragment:
#Override
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// access data fragment
final FragmentManager fm = getFragmentManager();
this.myDataFragment = (MyDataFragment)fm.findFragmentByTag(MyActivity.MY_DATA_FRAGMENT);
}
private void function1 () {
// read data from data Fragment
String dataStr = this.myDataFragment.myData;
// write data into data Fragment
this.myDataFragment.myData = "test_string";
}
private MyDataFragment myDataFragment = null;
Related
I have an activity that performs network operation, and on success, it needs to send data to view pager fragment.
So this is my structure.
Activity -> Home Fragment -> ViewPager [Fragment#1, Fragment#2]
Now the issue is that i can send data from Activity to Home Fragment, but i am unable to send data to View Pager Fragment, as activity does not have any direct connection with it.
To Fix this, I have taken network call from activity to fragment, and once i get response from service call, i pass data to viewPager fragment from Home Fragment.
This is working fine, and giving me desired result, but i am little confused if this is the right approach.
or there is some other recommended approach available that i can use, and pass data from activity to child fragment, or view Pager fragment, whose reference is not directly available to activity.
I would look at the new Android Architecture Components, specifically ViewModel and LiveData. https://developer.android.com/topic/libraries/architecture/viewmodel
The ViewModel class is designed to store and manage UI-related data in
a lifecycle conscious way.
You can create a shared view model that can be accessed by the activity and any fragment in that activity.
Example from the link:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item -> {
// Update the UI.
});
}
}
Notice that both fragments use getActivity() when getting the
ViewModelProvider. As a result, both fragments receive the same
SharedViewModel instance, which is scoped to the activity.
For your example, this avoids passing data down the hierarchy from activity to fragment then child fragment, they can all reference SharedViewModel.
I have an activity with collapsing AppBarLayout. In onCreate() method I am sending request to server to get some data. And depending what data I get - I need to dynamically in runtime choose what view to show to the user: 1. MyFragment1; or 2. TabLayout/ViewPager with FragmentPagerAdapter, which has two fragments in it. And I need to set some data to that fragments. But the issue is in next: I already have data and set it to fragments in my adapter, but fragment method onCreate is not yet called, and my layout is not initialized. That's how I get crash on populating data into layout view. So, how can I make somehow - fragment created and initialized it's fields first and only then setup it with data? Thanks.
private MenuFragment1 menu1Fragment1;
private MenuFragment3 menu1Fragment3;
private TabMenuAdapter adapter;
private void setupViewPager(ViewPager viewPager) {
menu1Fragment1 = new MenuFragment1();
menu1Fragment3 = new MenuFragment3();
adapter = new TabMenuAdapter(getSupportFragmentManager());
adapter.addFragment(menu1Fragment1, "Menu 1");
adapter.addFragment(menu1Fragment3, "Menu 2");
viewPager.setAdapter(adapter);
}
public onDataLoaded(String data)
{
//at this point, fragment is created, but it's View fields are NULL!!
menu1Fragment1.data = data;
}
#Layout(id = R.layout.content_shop_final)
public class ShopFinalTermsFragment extends BaseFragment {
private static final String SANS_SERIF_FAMILY_NAME = "sans-serif";
private static final String SANS_SERIF_MEDIUM_FAMILY_NAME = "sans-serif-medium";
private InfoModel InfoModel;
private RateModel RateModel;
#BindView(R.id.shop_final_nested_scroll_view)
NestedScrollView nestedScrollView;
#BindView(R.id.shop_final_pending_txt)
TextView pendingDurationTxt;
#BindView(R.id.shop_final_rate_cond_rv)
RecyclerView rateCondRv;
#BindView(R.id.shop_final_description_txt)
TextView descriptionTxt;
#Inject
ToolsManager toolsManager;
RateConditionsAdapter adapter;
private String getParams;
public static ShopFinalTermsFragment newInstance(String getParams, InfoModel shopInfoModel, RateModel RateModel) {
ShopFinalTermsFragment fragment = new ShopFinalTermsFragment();
Bundle args = new Bundle();
args.putString(SHOP_GET_PARAMS, shopGetParams);
args.putSerializable(INFO_MODEL_KEY, shopInfoModel);
args.putSerializable(MODEL_KEY, userCashbackRateModel);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
this.GetParams = getArguments().getString(SHOP_GET_PARAMS);
this.InfoModel = (InfoModel) getArguments().getSerializable(INFO_MODEL_KEY);
this.RateModel = (RateModel) getArguments().getSerializable(RATE_MODEL_KEY);
}
}
#Override
protected void setupInOnCreateView() {
nestedScrollView.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
conditionsTxt.setTypeface(Typeface.create(SANS_SERIF_FAMILY_NAME, Typeface.BOLD));
} else {
conditionsTxt.setTypeface(Typeface.create(SANS_SERIF_MEDIUM_FAMILY_NAME, Typeface.NORMAL));
}
}
#Override
protected void inject() {
ShopsComponent shopsComponent = DaggerShopsComponent.builder()
.applicationComponent(((BaseActivity) getActivity()).getApplicationComponent())
.build();
shopsComponent.inject(this);
}
public void setupWithData(InfoModel InfoModel, RateModel RateModel) {
//THIS METHOD IS COLLED FROM ACTIVITY'S onDataLoaded(InfoModel InfoModel, RateModel RateModel) method
setupShopInformation(shopInfoModel);
setCashBackRateModel(userCashbackRateModel);
}
}
You are using the dependency in a wrong way. It's not the activity that should call setupWithData on a fragment but it should be a fragment getting data from the activity (or other storage) instead. This way you will break this dependency on the fragment lifecycle which ends up being uninitialized.
Get the data from the server, store it where you need to, and update the UI from your activity. At this point you either show MyFragment1 or your TabLayout/ViewPager. If it's a TabLayout or a ViewPager, all you do is creating fragments and adding the to the layout or a corresponding pager adapter. That's it. You don't set the data at this point.
Now when your inner fragments populate in the pager adapter, they will go through onAttach, onCreate, onStart and onResume lifecycle methods. onResume is a good place to load the data. You either access it directly from the fragment, or get it from your outbound activity - depends on what makes more sense for you. If you need an activity reference, you can access it via getActivity() method in the fragment.
So in the fragment's onResume you will have something like:
setupShopInformation((YourActivity) getActivity()).getShopInfoModel());
setCashBackRateModel((YourActivity) getActivity()).getUserCashbackRateModel());
Although it would be even better to have it stored in some state class. But that will be a separate question.
Good luck!
I'm a newbie in android developer. I have a question about transfer with 3 fragments.
I have 3 fragments (A - B - C). I want o transfer data from A -> B -> C.
In each the fragment, data was been changed.
When user click BACK BUTTON, user want to return A with the updated data.
How to return fragment A with the update data?
Thanks.
Here is a sample idea how to achieve communication.
// activity classs
public class SampleActivity extends Activity implements OnFragmentChangeListener {
OnBackPressListener dataFragment;
public void onCreate(bundle){
android.app.FragmentManager fragmentManager=getFragmentManager();
android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
dataFragment = new DataFragment();
fragmentTransaction.add(R.id.audio_permission_button,dataFragment);
fragmentTransaction.commit()
}
#override
public void OnFragmentChange(Bundle bundle){
//here you go.
// write code to load new fragment with same idea. now you have bundle do what you want.
}
#Override
public void onBackPressed() {
// you can call this method from any click event, This just an sample idea.
dataFragment.OnActivityBackPress();
}
}
// interface to communicate with fragment
public interface OnFragmentChangeListener {
public void OnFragmentChange()
}
// fragment class
public class DataFragment extends Fragment implements OnBackPressListener {
OnFragmentChangeListener onFragmentChangeListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onFragmentChangeListener=(OnFragmentChangeListener) getActivity();
}
#Override
public void OnActivityBackPress() {
// pass you data to activity for loading new fragment or to refresh data.
Bundle bundle= new Bundle();
onFragmentChangeListener.OnFragmentChange(bundle);
}
}
// interface behave like mediator
public interface OnBackPressListener {
public void OnActivtiyBackPress();
}
Use interface to achieve this. Implements interface in fragment and activity, as it's a good way to communicate between fragment through activity. Then send the data through interface and extract the data from it.
You can use Interface class to communicate between fragment but it must make sure all the fragment is alive.
You can use SharedPreferences where you can save the data and retrieve it anywhere you like
I am working on Fragments, I would like to know whether it is possible to pass data from a fragment A to a fragment B directly, without forwarding the data to their attached Activity.
Use Bundle to send String:
//Put the value
YourNewFragment ldf = new YourNewFragment ();
Bundle args = new Bundle();
args.putString("YourKey", "YourValue");
ldf.setArguments(args);
//Inflate the fragment
getFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
In onCreateView of the new Fragment:
//Retrieve the value
String value = getArguments().getString("YourKey");
There are number of ways you can do that. One of them is by sending data in arguments like this:
private int data;
private static String PARAM_MY = "param";
public static MyFragment newInstance(int data) {
MyFragment fragment = new MyFragment ();
Bundle args = new Bundle();
args.putInt(PARAM_MY , data);
fragment.setArguments(args);
return fragment;
}
and you can retreive it onCreate() :
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
data = getArguments().getInt(PARAM_MY);
}
}
Other way is to use Interfaces.
I don't think so. Every fragment is attached to an activity, there's no direct connection between two fragments, unless they are connected to the same activity. If you want to communicate between fragments, you'll have to define interfaces insides fragment and make the attached activity implement the interfaces. In the attached activity you have the two fragments instances, so you can pass (using the activity) data between fragments. It's strictly recommended by best practices, take a look at documentation for details:
https://developer.android.com/training/basics/fragments/communicating.html
If the activity is the same you can keep the data in the activity.
Activity
public class MyActivity extends Activity {
private MyObject myObject = new MyObject();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
...
}
public MyObject getMyObject() {
return myObject;
}
}
Fragment
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
MyActivity activity = (MyActivity) getActivity();
MyObject myObjectFromActivity = activity.getMyObject();
return view;
}
}
The main question would be what are you trying to achieve and what sort of data are you trying to pass?
One of the apps I wrote for work used the Main Activity to hold all the controllers so they could easily be accessed from any view, or framgment, or associated controller. A good example would be a data-controller holding all shared app data.
Are you looking to pass strings and ints, or more complex objects?
There are number of ways to achieve this
1 : Using Bundle
Link 1 - Explains How to transfer data using bundle and other methods
2 : Using Activities & Fragment
Link 2 - With help of activites ,interface and fragments
3: Using Shared Preferences
Shared Preferences Link
In my app, I have Fragment which is inside ViewPager. Fragment contains RecyclerView with list of data fetched from web api based on user selection.
On my Fragment onSaveInstanceState I save list data to Bunde, to keep the data on configuration changes etc.
public void onSaveInstanceState(Bundle savedState) {
super.onSaveInstanceState(savedState);
savedState.putParcelableArrayList(LIST_STORAGE_KEY, new ArrayList<>(mItemAdapter.getModels()));
}
Now I have started to see TransactionTooLargeException on my app error reporting.
It seems that in some cases the list which Im putting to Bundle, is too large (as it is collection of quite complex objects).
How should I handle this case? How to store (and restore) my Fragment state.
Is it ok to use setRetainInstance(true) on Fragments inside ViewPager?
To preserve big chunks of data, Google is suggesting to do it with Fragment that retain instance. Idea is to create empty Fragment without view with all necessary fields, that would otherwise been saved in Bundle. Add setRetainInstance(true); to Fragment's onCreate method. And than save data in Fragment on Activity's onDestroy and load them onCreate. Here is and example of Activity:
public class MyActivity extends Activity {
private DataFragment dataFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag("data");
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
#Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
And example of Fragment:
public class DataFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
If you don't want your fragment to use setRetainInstance(true), then you can add an empty fragment with setRetainInstance(true) to your activity. This is useful since child fragments cannot use setRetainInstance(true).
Example:
public class BaseActivity extends Activity {
RetainedFragment retainedFragment;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag("retained_fragment");
if (retainedFragment == null) {
retainedFragment = new RetainedFragment();
getFragmentManager().beginTransaction().add(retainedFragment, "retained_fragment").commit();
}
}
public <T> T getState(String key) {
//noinspection unchecked
return (T) retainedFragment.map.get(key);
}
public void saveState(String key, Object value) {
retainedFragment.map.put(key, value);
}
public boolean has(String key) {
return retainedFragment.map.containsKey(key);
}
public static class RetainedFragment extends Fragment {
HashMap<String, Object> map = new HashMap<>();
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}
Then, in your fragment, you can cast getActivity() to your Activity class and use saveState(String, Object) and getState(String) to save your list.
There are other discussions on this which can be found at the following locations:
What to do on TransactionTooLargeException
android.os.TransactionTooLargeException on Nougat (Accepted answer suggests setRetainInstance(true)).
setRetainInstance() is the best way to achieve that without side effects. Using static will cause memory leak and there is no use of saving the state in onSaveInstanceState() and getting it back since, setRetainInstance() does that for you.
So create a field for the list in fragment class and always check for null or size of list to begin operation of fetching latest data