I have a fragment A and a fragment B.
Fragment A is called from 2 different screens (which are also fragments).
Now, fragment A passes arguments to fragment B, using setArguments(bundle), whenever called.
Like this:(These lines are written in Fragment A)
Bundle bundle = new Bundle();
bundle.putString("bookId", bookListItems.get(position).getBookId());
bundle.putString("bookName", bookListItems.get(position).getBookName());
Fragment b = new B();
b.setArguments(bundle);
replaceFragment(b, bundle);
and the function replaceFragment is as follows:
private void replaceFragment (Fragment fragment, Bundle bundle) {
String backStateName = fragment.getClass().getName();
FragmentManager manager = mActivity.getFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
if (!fragmentPopped) { //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.frame_container, fragment);
ft.addToBackStack(backStateName);
ft.commit();
} else {
fragment.getArguments().putString("bookId", bundle.getString("bookId"));
fragment.getArguments().putString("bookName", bundle.getString("bookName"));
}
}
Now, the problem is, when Fragment A is called from Screen 1, it works fine and passes right arguments to fragment B. But when it is called from Screen 2, the arguments passed to B are never updated, it always opens up the last state of fragment B.
Please help.
you could create in your Fragment's subclass which takes the new data, for instance, and call this method from the hosting Activity when you want to update it
public void updateData(String bookName, String bookId) {
if (getView() == null) {
return;
}
((TextView)getView().findViewById(R.id.texviewid)).setText(bookName);
}
In your Activity you will have to keep a reference to the specific subclass, as class member.
Another approach could make use of a BroadcastReceiver. In your Fragment register it with a specific IntentFilter and from your Activity use a LocalBroadcastManager to broadcast locally the intent with the changes. You could still use the same method, to update the UI. When onReceive is invoked, check the fragment status, and if it is in a correct status, extracts the data from the Intent, and call the method. E.g.
#Override
public void onReceive(Context context, Intent intent) {
if (isRemoving() || isDetached() || !isAdded()) {
return;
}
// extract data from intent
// call updateData
}
Related
I have found a specific case when FragmentManager.findFragmentByTag("tag") returns null.
I have a gut feeling it has to do with timing?
I have a networking library with the following callbacks:
onStart()
{
Utils.ShowLoadingDialog("loading");
}
onFinnish()
{
Utils.DismissLoadingDialog("loading");
}
Then in my Utils class I have the following code:
public void showLoadingDialog(String title, String message, String tag) {
DialogFragment loadingDialogFragment = new LoadingDialogFragment();
Bundle args = new Bundle();
args.putString(CommonBundleAttributes.CONNECTING_ACTIVITY_DIALOG_TITLE, title);
args.putString(CommonBundleAttributes.CONNECTING_ACTIVITY_DIALOG_MESSAGE, message);
loadingDialogFragment.setArguments(args);
FragmentTransaction transaction = fragManager.beginTransaction();
loadingDialogFragment.show(transaction, tag);
}
public void dismissLoadingDialog(String tag) {
DialogFragment dg = (DialogFragment) fragManager.findFragmentByTag(tag);
if (dg != null) {
// this reference isn't null so the dialog is available
dg.dismissAllowingStateLoss();
}
}
Now this generally works fine. However in cases when the network layer detects there is no internet. It will throw an error and then immediately call onFinnish(). In this case the Utils.DismissDialog(tag) does nto find the fragment and therefore does not dismiss it?
You can use executePendingTransactions() to wait for the fragment transaction to come through.
public void dismissLoadingDialog(String tag) {
fragManager.executePendingTransactions();
DialogFragment dg = (DialogFragment) fragManager.findFragmentByTag(tag);
if (dg != null) {
// this reference isn't null so the dialog is available
dg.dismissAllowingStateLoss();
}
}
Use TRY-CATCH or even IF statement to check current internet-connection.
This can be case with the committing the transaction.
Just check if in your show method if you have commit the transaction for Dialog fragment or not.
transaction.commit();
Until that your fragment manager does not have fragment to be added in that.
Also you need to make sure you are finding fragment from same Activity's fragment manager to which you committed fragment.
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).
In my FirstActivity, user will log in. If the user exists in the database, it is loaded and should be "passed" to the SecondActivityFragment which is within the SecondActivity. The need is to check whether the user is with incomplete register, if so, the toolbar will display a warning menu item telling it to complete the registration.
┌FirstActivity
├─SecondActivity
└──SecondActivityFragment
Every tutorial that I see showing how pass data through Activity and Fragment talking about replace fragments and so on, I think that's not my case.
I created newInstance() on my SecondActivityFragment but I'm kinda lost.
public static SecondActivityFragment newInstance(User user) {
Bundle args = new Bundle();
args.putSerializable("user", user);
SecondActivityFragment fragment = new SecondActivityFragment();
fragment.setArguments(args);
return fragment;
}
And when user clicks in login button
if (userExists()) {
userManager = new UserManager();
User user = userManager.getByEmailPwd(editEmail.getText().toString(), editPwd.getText().toString());
Intent secondActivity = new Intent(getContext(), SecondActivity.class);
SecondActivityFragment.newInstance(user);
startActivity(secondActivity);
}
Try to put user into your secondActivity intent.
Then in the SecondActivity's onCreate method get the user class using getIntent().getSerializable() and create an instance of SecondActivityFragment.
Calling
SecondActivityFragment.newInstance(user);
that way, will not cause any effects in what will got presented.
If you want to present the fragment in the context of Second activity, consider passing the data that the fragment need to know to the Second activity - it should be sth like:
secondActivity.putSerializable("user", user)
Then in SecondActivity's onCreate, or in other method, you have to replace fragment being displayed, for your SecondActivityFragment instance:
User user = null;
final Bundle args = getIntent().getExtras();
if(args.getSerializable("user") instanceof User){
user = (User)args.getSerializable("user");
}
if(user != null){
Fragment secondActivityFragment = SecondActivityFragment.newInstance(user);
FragmentMenager fragmentMenager = getFragmentMenager();
FragmentTransaction fragmentTransaction = fragmentMenager.beginTransaction();
fragmentTransaction.replace(R.id.frame_for_your_fragment, secondActivityFragment);
}
This might have already answered but I am still troubling with a function like this. Let's say I have activity A and activity B. B holds a viewpager with several fragments in it. I would like to call a function in the fragment held by activity B from activity A.
I used callbacks many times to communicate between activites and fragments but every single time it was only the fragment and its holder activity. I do not want to make a static method (the callback listener cannot be static anyway) so it causes a headache for me. The simple static solution to make a static method in the fragment and have it called from the other actually works very well, but I am not sure if it was a good idea as I need to change several things static.
So communicating between Activity B and its fragments is ok, but I cannot call this method in Activity A.
Activity B:
public class ActivityB extends FragmentActivity implements Fragment1.OnWhateverListener
{
...
#Override
public void onWhateverSelected(int position) {
//stuff, here I can call any function in Fragment 1
}
}
The following code snippet is a wrong solution (doesnt even work) but makes a better picture what I would like to do.
Activity A:
ActivityB ab = new ActivityB ();
ab.onWhateverSelected(number);
So how can I do this?
Thank you!
EDIT
Activity A: the method I call
Bundle args = new Bundle();
args.putString("ID", id); // the data to send
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, MainActivity.class);
frag_args.putExtra("args", args);
startActivity(frag_args);
Activity B:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
...
processIntent(getIntent()); //last line of onCreate, always gets called here
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this never gets called here only in OnCreate
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra("args");
if (args != null) { // check if ActivityB is started to pass data to fragments
String id = args.getString("ID");
Log.i("ID_FROM", "id: " + id); //works well
if (id != null) {
List<Fragment> fragments = new ArrayList<Fragment>();
fragments = getSupportFragmentManager().getFragments();
//NULLPOINTER for the following line
FragmentMainDiscover fr = (FragmentMainDiscover) fragments.get(0);
fr.RefreshHoverView(id);
}
}
}
You are right to stay away from statics. Way too risky, for visual objects that may or may not be on screen.
I would recommend going through activity B, since it is the parent of your target fragment. Create an Intent that starts activity B, and include an intent extra that tells activity B what it should do to the target fragment. Then activity B can make sure that the fragment is showing, and pass the information on to it.
One other idea to pass the info to the fragment is to use setArguments, rather than direct calls. This is a nice approach because Android will restore the arguments automatically if the activity and its fragments are removed from memory.
Does this make sense? Do you want the code?
EDIT
To use arguments, you still need to have activity A go through activity B. This is because activity A doesn't know if activity B, and all its fragments, is running unless it sends it an Intent. But you can include data targeted for the fragments, by putting them inside the intent. Like this:
public class ActivityA extends Activity {
public static final String KEY_FRAG = "frag"; // tells activity which fragment gets the args
public static final String KEY_ARGS = "args";
public static final String KEY_MY_PROPERTY = "myProperty";
public void foo() {
Bundle args = new Bundle();
args.putString(KEY_FRAG, "frag1Tag"); // which fragment gets the data
args.putCharSequence(KEY_MY_PROPERTY, "someValue"); // the data to send
// Send data via an Intent, to make sure ActivityB is running
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, ActivityB.class);
frag_args.putExtra(KEY_ARGS, args);
startActivity(frag_args);
}
}
public class ActivityB extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//TODO configure activity including fragments
processIntent(getIntent()); // this call is in case ActivityB was not yet running
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this call is in case ActivityB was already running
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra(ActivityA.KEY_ARGS);
if (args != null) { // check if ActivityB is started to pass data to fragments
String fragTag = args.getString(ActivityA.KEY_FRAG);
if (fragTag != null) {
Fragment frag = getSupportFragmentManager().findFragmentByTag(fragTag);
frag.setArguments(args);
//TODO either show the fragment, or call a method on it to let it know it has new arguments
}
}
}
}
public class Fragment1 extends Fragment {
public static final String TAG = "frag1Tag";
#Override
public void onResume() {
super.onResume();
Bundle args = getArguments();
String value = args.getString(ActivityA.KEY_MY_PROPERTY);
...
}
}
Currently trying to use a bundle to transfer information from both my IncomeFragment and ExpenseFragment to HomeFragment but I'm unsure as to how to do it. I've tried implementing doubleA's code which he provided.
This is my onAcceptClicked method from my MainActivity which takes the value of the total income/expense from the relevant fragment and transfers it to the HomeFragment:
public void onAcceptClicked(String fragment, String total) {
final FragmentManager fm = getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
if (fragment == "income") {
HomeFragment homeFrag = new HomeFragment();
Bundle incomeBundle = new Bundle();
incomeBundle.putString(IncomeFragment.TAG, total);
//homeFrag.newInstance(total);
ft.replace(R.id.content_layout, homeFrag, HomeFragment.TAG);
ft.commit();
}
else if (fragment == "expense"){
HomeFragment homeFragment = new HomeFragment();
Bundle expenseBundle = new Bundle();
expenseBundle.putString("bundleIncome", total);
homeFragment.setArguments(expenseBundle);
ft.replace(R.id.content_layout, homeFragment, HomeFragment.TAG);
ft.commit();
}
}
I have an interface in my IncomeFragment which I use to communicate with my MainActivity so I can use the onAcceptClicked method to transfer my totals over. I plan on basically doing the same thing with my ExpenseFragment. The code below is a snippet from my IncomeFragment:
public interface SendIncomeData {
public void onAcceptClicked(String fragment, String total);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_accept:
//Toast.makeText(getActivity(), stringIncomeTotal, Toast.LENGTH_LONG).show();
sendIncomeData.onAcceptClicked("income", stringIncomeTotal);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Unfortunately I'm getting an error with this line of code
sendIncomeData.onAcceptClicked("income", stringIncomeTotal);
This is the error
java.lang.NullPointerException: Attempt to invoke interface method 'void mos.myapplication.IncomeFragment$SendIncomeData.onAcceptClicked(java.lang.String, java.lang.String)' on a null object reference
I don't know why it's saying there's a null object reference and/or how I could fix this error.
I'm guessing there's probably going to be an error displaying my totals in my HomeFragment because I haven't called the method below anywhere within my code in my MainActivity or my IncomeFragment / ExpenseFragment. The reason I haven't used it is because I wasn't sure how to get it so that the HomeFragment opens first when the application is launched.
static HomeFragment newInstance(String total)
{
HomeFragment frag = new HomeFragment();
Bundle args = new Bundle();
args.putString(TAG, total);
frag.setArguments(args);
return frag;
}
I don't even mind starting from scratch, as long as I can transfer totals and display them from IncomeFragment > HomeFragment and also ExpenseFragment > HomeFragment
You need to check if bundle is null in the first place:
bundle = this.getArguments();
if (bundle != null)
{
// continue with your logic
}
Also, all Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly. See below links for more info:
http://developer.android.com/training/basics/fragments/communicating.html
http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
A lot of people will argue that using interfaces is the best way to do that, which isn't really wrong but there are much simpler ways. Edit: not necessarily simpler
-One way is to get a reference to the activity within the fragment by calling
this.getActivity();
then in your activity class you can have a method that passes the data to the other fragment since it has a reference to both fragments.
-Then there's an even better way to do it actually (though that first part is probably helpful too):
In the fragment that has the data that needs to be moved, you can get a reference to the other fragment from the FragmentManager. This assumes that you created that fragment with a string ID like so:
Fragment otherFragment;
getSupportFragmentManager().beginTransaction().add(otherFragment, "otherFrag").commit();
Then in the fragment with the data, you'll do:
FragmentActivity fragmentActivity = (FragmentActivity)getActivity();
OtherFragment otherFragment = (OtherFragment)fragmentActivity.getSupportFragmentManager().findFragmentByTag("otherFrag");
Then with a reference to OtherFragment (in this case), you can do something like:
otherFragment.dataString = "myData";
But you'll probably want to have a test to make sure the other fragment doesn't come back null, and if it is null you might just want to create it then and there since you already have a reference to the FragmentManager.
Edit: I'm just going to say that I like this method more, subjectively.