I want to pass a complex object between activity and fragments as well as fragments and fragments. Currently, the main activity create a fragment input object and set that as a member of the fragment needed to be open. Similarly, when another fragment wants to load another fragment it creates fragment input and notifies the main activity. See Main and child fragment code below. My question, is this correct implementation. Sometimes I encountered input being null in child activity, if the activity pauses and restarts.
Please tell me what I have done wrong, whats the best way to pass data.
public class FragmentInput {
public String url = "";
public String title = "";
public String time = "";
... other memebers
}
Main Activity
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
BaseFragment fragment = new LandingFragment();
**FragmentInput input = new FragmentInput();
input.stringinput = stringinput;
fragment.input = input;
fragmentTransaction.replace(R.id.fragment, fragment);
fragmentTransaction.commit();**
public void replaceFragment(BaseFragment fragment) {
if (fragment == null)
return;
if (fragment instanceof firstFragment) {
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(0, 0);
fragmentManager.popBackStackImmediate(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
} else {
String ttag = fragment
.getClass().toString();
Fragment tempF = fragmentManager.findFragmentByTag(ttag);
if (tempF != null)
return;
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.fragment_enter,
R.anim.fragment_exit, R.anim.fragment_leftenter,
R.anim.fragment_leftexit);
fragmentTransaction.replace(R.id.fragment, fragment, ttag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
ChildFragment
#Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
try {
activity = getActivity();
resource = activity.getResources();
view = getView();
**if (input != null) {
String url= input.url;**
button.onclick(){
FragmentInput input = new FragmentInput();
input.url = path;
input.title = resource.getString(R.string.txt_from_gallery);
**BaseFragment fr = new otherFragment();
FragmentChangeListener fc = (FragmentChangeListener) getActivity();
fr.setInput(input);
fc.replaceFragment(fr);**
}
}
If your fragments attach to same activity, you can store your objects in activity and access to objects like below:
((YourActivity)getActivity()).getYourObjects();
If you are storing your objects in bundle in you activity i recommand to call the code sample i gave above in onActivityCreated() method of your fragments to avoid null pointer exception.
If you want to pass your objects between activities or fragments in bundle. You should implement Parcelable to your objects and pass them.
What's Parcelable?
http://developer.android.com/reference/android/os/Parcelable.html
Parcelable is more efficient but you can check Serializable:
http://developer.android.com/reference/java/io/Serializable.html
It's not a good design to pass large objects in bundle.
Also you can pass your objects with interfaces or you can pass them with bus events. You can check Guava or OttoBus.
http://square.github.io/otto/
In this case you can use static holder (it's a bit similar to a singleton pattern).
Create a new class
public class Holder
{
private static FragmentInput input = null;
public static void setInput(FragmentInput value) { this.input = value; }
public static FragmentInput getInput() { return input; }
}
In your main activity, after you create your new FragmentInput object
hold it on the Holder
Holder.setInput(input);
And you can access it anywhere, simply call
FragmentInput myInput = Holder.getInput();
Related
I was working on communication between multiple fragments in a activity stack.
I have figured out 2 ways to do this.
Through interfaces
Through Bundle setarguments
Bundle bundle = new Bundle();
bundle.putBoolean("Status",trur);
Fragment fragment = getFragmentManager().findFragmentByTag(bottomfragment.class.getName());
if(fragment!=null) {
fragment.setArguments(bundle);
}
I felt the 2nd approach easy.Since Google recommends 1 st approach
Can anyone help me with the problems I may face by following 2nd approach.
You are mixing the both the ways.
1. through interfaces is if you want to communicate from fragment to activity or fragment to fragment(via activity)
2. set argument is if you want to pass arguments while starting the fragment. you can call methods of fragment using the instance you get from fragment id/tag
Please referfragment communication
Try to communication between two fragments like this:
1) Create Interface like this:
public interface FragmentChangeListener {
void changeFragment(Fragment fragment);
}
2) Update MainActivity like this:
public class MainActivity extends AppCompatActivity implements FragmentChangeListener
{
//Activity code
------
#Override
public void changeFragment(Fragment fragment) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tr = fm.beginTransaction();
tr.replace(R.id.frame_container, fragment);
tr.commitAllowingStateLoss();
}
}
3) Create First Fragment:
public class FirstFragment extends Fragment
{
// call another freagment like this
//in your oncreateview method:
SecondFragment
Bundle b = new Bundle();
b.putSerializable(SELECTED_ITEM, true);
SecondFragment second = SecondFragment.newInstance(b);
FragmentChangeListener fc = (FragmentChangeListener) getActivity();
fc.changeFragment(second);
}
4) Second Fragment:
public class SecondFragment extends Fragment
{
public static SecondFragment newInstance(Bundle bundle) {
SecondFragment fragment = new SecondFragment();
if (bundle != null)
fragment.setArguments(bundle);
return fragment;
}
//another fragment related code
//In your OncreateView like this:
if (getArguments() != null)
boolean temp = getArguments().getBoolean(IntentParameter.SELECTED_ITEM);
}
Hope this explanation help you :)
Argument (Bundle) should be passed to Fragment only initially (when Fragment's object is created by default constructor). Calling setArguments method on already added Fragment will cause IllegalStateException. See body of setArguments method:
public void setArguments(Bundle args) {
if (mIndex >= 0 && isStateSaved()) {
throw new IllegalStateException("Fragment already active and state has been saved");
}
mArguments = args;
}
If you want to change something in Fragment A from Fragment B :
a) Get an object of A inside B using
getFragmentManager().findFragmentByTag("FRAGMENT_A_TAG");
Or
getFragmentManager().findFragmentById(FRAGMENT_A_CONTAINER_ID);
Cast returned object to A and call proper method on it. (It's the simplest way, but after it, A and B become highly coupled);
b) Alternatively, you can write mentioned logic inside method of Activity, which contains these 2 Fragments, get reference of this Activity inside B using getContext() casted to container Activity and call mentioned method on it (It kills reusability, because if you want to have A and B on other Activity, casting getContext() will cause ClassCastException);
c) The best way, to communicate between Fragments is to create interface, implement container Activity by this interface, get reference of this interface inside B and call proper method on it. (You can implement as many activities as you want by this interface, so it's reusable approach and A and B are loosely coupled).
I am trying to prevent my DialogFragment opening twice. Here is what I do:
I try to keep only one instance of my fragment. I create and add my fragment like this:
//MyFragment.java
public static MyFragment mInstance;
public static void instantiateFragment() {
MyFragment myFragment = MyFragment.getInstance();
if(!myFragment.isAdded()) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(myFragment, TAG);
ft.commit();
}
}
private static MyFragment getInstance() {
if(mInstance == null) {
mInstance = new MyFragment();
}
return mInstance;
}
And when a button is clicked, I intentionally try to add fragment twice like this:
MyFragment.instantiateFragment();
MyFragment.instantiateFragment();
But I get IllegalStateException: Fragment already added. Any ideas about that?
Thanks.
Indeed it's a problem with asynchronous commit of transactions, so as #Android jack stated you can use executePendingTransactions() like in this answer,
or even better use commitNow(),
or try something like this:
public static void instantiateFragment() {
Fragment myFragment = getSupportFragmentManager().findFragmentByTag(TAG);
if (myFragment == null) {
myFragment = MyFragment.getInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(myFragment, TAG);
ft.commit();
}
}
I think this has to do with the asynchronous behaviour of fragment transactions.Fragment Transactions are committed asynchronously. So at first call, your fragment is added but it is committed asynchronously.Again in your next call your fragment is not added as it is not committed yet so !myFragment.isAdded() returns false.Then while adding the fragment the previous transaction is committed due to which it raises exception.
Try to use this
getFragmentManager().executePendingTransactions();
before your (!myFragment.isAdded()) code.
I have an Activity with have a different stack of fragments. One fragment, which has a list of items, starts an activity of detail. In this activity I need the fragment instance to do something (related to dagger 2) when the activity is created.
I have tried the findFragmentById and findFragmentByTag methods but returns null.
I have this code in my activity:
protected void initDI() {
ContactsFragment contactsFragment = (ContactsFragment) getSupportFragmentManager().findFragmentById(fragmentId);
ContactsFragmentComponent fragmentComponent = contactsFragment.getFragmentComponent();
DetailContactActivityComponent subcomponent = fragmentComponent.createSubcomponent(new DetailContactActivityModule());
subcomponent.injectDetailContactActivity(this);
}
How can I get the instance of the fragment in the activity?
EDIT:
The problem is when I start a new activity and I get the getFragmentManager instance, this instance is different from the fragmentManager of the fragment that starts the activity.
If you could not find them this way, then try to look through all backstack fragments:
boolean foundMyFragment = false;
String fragmentBackstackTag = "";
FragmentManager fm = getSupportFragmentManager();
for(int entry = 0; entry < fm.getBackStackEntryCount(); entry++){
fragmentBackstackTag = fm.getBackStackEntryAt(entry).getName();
Log.i(TAG, "Found fragment: " + fragmentBackstackTag);
// check if null, because sometimes fragmentBackstacktag is null
if (fragmentBackstackTag != null) {
if (fragmentBackstackTag.equals("fragmentTagYouAreSearching")) {
foundMyFragment = true;
// get fragment from backstack
}
}
}
I am tyring to add a fragment in a navigation Drawer and some of the data have to pass to the fragment,
but getArgument returns null and I still cannot solve the problem after reading the similar question.
In my drawer class, the fragment is added by
Bundle args = new Bundle();
args.putString("username",username);
args.putString("password",password);
UserLoginFragment alreadyLoginFragment = new UserLoginFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
alreadyLoginFragment.setArguments(args);
fragmentTransaction.add(R.id.drawer_User_Container, alreadyLoginFragment, "alreadyLogin");
fragmentTransaction.commit()
;
And in the fragment, the argument is get by :
String username;
String password;
public UserLoginFragment(){
Bundle args=getArguments();
if(args!=null){
Log.d("test","have args");
}
else{
Log.d("test","no args");
}
}
And the log will be no args
Dont call getArgument() in the constructor, at the time the constructor is called, the argument bundle has not yet been set, so it will return null every time no matter what you do. Call it like this
#Override
public void onAttach(Activity act)
{
Bundle args = getArguments();
}
You are just recieving the argument. You must recieve the content that was passed in the argument.
String username = getArguments().getString("username");
String password = getArguments().getString("password");
I am currentley launching new activities and passing data liek this:
Intent myIntent = new Intent(c, TastePage.class);
myIntent.putExtra("taste", tempTaste);
c.startActivity(myIntent);
and then retrieving the data in the new activity with:
//get data from listview
Bundle extras = getIntent().getExtras();
String taste = extras.getString("taste");
In order to follow android design guidelines better I am implementing a navigation drawer and changing my activities into fragments. I am just a little confused on how to pass data between fragments.
For loading new fragments I am using this code:
FragmentManager man=getFragmentManager();
FragmentTransaction tran=man.beginTransaction();
Fragment_two=new BPTopTastes();
tran.replace(R.id.main, Fragment_two);//tran.
tran.addToBackStack(null);
tran.commit();
what can I implement into that code to pass data to the new fragment, and how can I retrieve that data in the new fragment?
Use a bundle:
Send data from source fragment
Fragment fragment = new Fragment();
final Bundle bundle = new Bundle();
bundle.putString("user_name", myusername);
fragment.setArguments(bundle);
And read from target fragment
Bundle args = getArguments();
if (args != null && args.containsKey("user_name"))
String userName = args.getString("user_name");
It is not the best idea but you can also use setters to set some values. You don't have to use Bundle.
public class YourFragment extends Fragment{
private String mUserName;
public void setUserName(String userName) {
mUserName = userName;
}
}
......
FragmentTransaction tran = getSupportFragmentManager().beginTransaction();
YourFragment fragment = new YourFragment();
fragment.setUserName(myUserName);
tran.add(R.id.main, fragment, "frag");
tran.addToBackStack(null);
tran.commit();
you could use Singleton Class to pass information between any component on your android process
you could access them from your services / activities / fragments ....
for example
class GlobalVars{
private GlobalVars global;
private String taste;//generate get/set
public static GlobalVars getinstance(){
if(global==null){global = new GlobalVars();}
return global}}
}
//By Calling
GlobalVars.getinstance()
//you will have a common access to the variables inside the class