Android: Callback to fragment from DialogFragment? - android

I know to pass back something from a fragment to its calling activity you can use onAttach which has the "activity" parameter. You can set the activity to variable and call an interface on it later. So passing data from the fragment back to the activity. All great.
I would like to do the same thing but this time i have a standard fragment and I want to call a DialogFragment and then have the DialogFragment call back to the original fragment but I can't use onAttach is wants a Activity.
Anyone know the best way of doing this ?
Thanks

Obviously you could just make things public in your activity and set them from your fragment. But then you have to keep references to your activity, and possibly have unwanted public variables and/or setters.
You could use EventBus and you would not need any of that.
In your activity you need to register an event
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
// This method will be called when a MessageEvent is posted
public void onEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
Then you can simply call your event from your fragment or anywhere you like,
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
Some more information on EventBus can be found here
And another possibly useful tutorial.

One way to contact another fragment from there is to access it in the implemented method in your Activity:
//In your Activity...
#Override
public void callbackFromFragmentA(){
FragmentB fragment = (FragmentB) getFragmentManager.findFragmentById(android.R.id.content);
if (fragment != null) {
fragment.callFragmentMethod();
}
}

Related

Otto unregistering the same instance

I have a BaseFragment which within it's onCreateView method, creates a MyObject class. Both of these are inside a ViewPager.
Two different fragments extends from the BaseFragment - FragmentA, FragmentB.
This means FragmentA and FragmentB both have their own instances of the MyObject object.
Within the BaseFragment, I call myObject.initialise(); on the MyObject object from the onStart(); method and cleanUp(); from the onStop();
#Override
public void onStart()
{
super.onStart();
myObject.initialise();
}
#Override
public void onStop()
{
myObject.cleanUp();
super.onStop();
}
Again - this lives inside the BaseFragment so both FragmentA and FragmentB have this in their lifecycle.
The initialise(); function and cleanUp(); functions look like this:
#Override
public void initialise()
{
BusManager.register(this);
}
#Override
public void cleanUp()
{
BusManager.unregister(this);
}
FragmentA will generally close first and it successfully unregisters. When FragmentB closes however, it crashes because it think this was not registered.
I checked the memory address of this and it appears that it tries to unregister the same thing twice.
Missing event handler for an annotated method. Is class com.example.app.MyObject registered?
Why is it doing this? I have made sure that MyObject is a new instance.
For the comment above, note that onDestroy() is not necessary called:
https://developer.android.com/reference/android/app/Activity.html#onDestroy()
You should not count on that for Otto's register / unregister call.
In regarding to Subby's question: I've had scenarios where onStart() / onStop() being called twice. What I ended up is placing a try-catch block. Definitely not a clean solution, but that's how I do before finding out why the lifecycle is messed up.

What is the best approach to call register/unregister eventbus on fragments?

I'm brand new using Event Bus from otto lib, So far I created a Event Bus Singleton class, which I'm using in several parts of my code. Now I'm working on a fragment view, But I still have a question, regarding:
When is the best time to register/unregister my event bus?
In a couple of posts I read that onStart() and onStop(), but without any specific reason why.
public class SomeFragment extends Fragment {
#Override
public void onStart() {
super.onStart();
EventBusSingleton.register(this);
}
#Override
public void onStop() {
super.onStop();
EventBusSingleton.unregister(this);
}
}
If I follow the same approach as in the activities doing the call onResume() and onPause() works fine as well.
public class SomeFragment extends Fragment {
#Override
public void onResume() {
super.onResume();
EventBusSingleton.register(this);
}
#Override
public void onPause() {
super.onPause();
EventBusSingleton.unregister(this);
}
}
What could be the potential risk(if exist) from each call way?
onPause()/onResume() is called when your activity does not have the focus anymore but could still be visible (think a dialog or alert on top of your activity).
onStop()/onStart() is called when your activity is not visible anymore.
Which one to use depends your use case. I believe it's not really a problem to have callbacks executed while in the paused state so I would just put the register/unregister in onStop()/onStart() but if you really want to make sure, you can put them in onPause()/onResume().
My problem was what my fragmets had two instances for bad coding, y delete de uneccesary instance and it solve the problem

Fragment and DialogFragment Lifecycle Relationship

I have Fragment "A" where I have an ImageButton in place. Upon clicking this button a DialogFragment "B" is called to the foreground where Fragment "A" is partially visible in the background. DialogFragment "B" presents the user with a list of choices. Upon clicking a specific choice DialogFragment "B" is dismissed via Dismiss() and Fragment "A" becomes fully visible again.
During this action I need to update the ImageButton on Fragment "A" to represent the user's choice made on DialogFragment "B" (basically a new image for the ImageButton).
Am I correct in thinking the right place to update the ImageButton on Fragment "A" is during OnResume? Does Fragment "A" go into OnPause while FragmentDialog "B" is being shown? Therefore upon returning from DialogFragment "B", Fragment "A" would trigger its OnResume and that's where I should make the update changes to the ImageButton being presented to the user?
I hope my explanation is clear. Any detailed help on where and how I should be updating the ImageButton would be highly appreciated.
With the addition of ViewModels and LiveData solving this problem just got easier. Create a viewModel which both fragments reference. Put the next line in the OnCreate of the fragments. Can also be in onCreateDialog of the dialogfragment.
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
When the dialog is dismissed, call a method on myViewModel, which updates a LiveData variable:
dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(PositiveButtonPressed);
}
});
dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(NegativeButtonPressed)
}
});
In the viewModel the method sets a MutuableLiveData variable for example to the image to be shown.
void SetButtonPressed(int buttonPressed){
if (buttonPressed==positiveButtonPressed){
imageToBeShown.setValue(image A);
}
else{
imageToBeShown.setValue(image B);
}
}
Set an observer to LiveData variable in onActivityCreated:
myViewModel.imageToBeShown().observe(getViewLifecycleOwner(), new Observer<Image>() {
#Override
public void onChanged(#Nullable Image image) {
button.setBackground(image);
}
}
});
Of course you can implement a getter method and keep the MutuableLiveData variable private. The observer then just obeserves the getter methode.
I had same problem when tried with Interface-Callback method but OnResume of Fragment didn't got triggered when DialogFragment was dismissed since we are not switching to other activity.
So here Event Bus made life easy. Event Bus is the easiest and best way to make communication between activities and fragments with only three step, you can see it here
This is nothing but publish/subscribe event bus mechanism. You will get proper documentation here
Add EventBus dependency to your gradle file -
compile 'org.greenrobot:eventbus:x.x.x'
OR
compile 'org.greenrobot:eventbus:3.1.1' (Specific version)
For the above scenario -
Create one custom POJO class for user events -
public class UserEvent {
public final int userId;
public UserEvent(int userId) {
this.userId = userId;
}
}
Subscribe an event in Fragment A whenever it is posted/published from DialogFragment or from somewhere else -
#Subscribe(threadMode = ThreadMode.MAIN)
public void onUserEvent(UserEvent event) {
// Do something with userId
Toast.makeText(getActivity(), event.userId, Toast.LENGTH_SHORT).show();
}
Register or Unregister your EventBus from your Fragment A's lifecycle especially in onStart and onStop respectively -
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
In the end, on clicking specific item, Publish/Post your event from DialogFragment -
EventBus.getDefault().post(new MessageEvent(user.getId()));
Fragment A won't go into onPause so onResume won't get called
http://developer.android.com/guide/components/fragments.html
Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity
in which this fragment lives is still visible (the foreground activity
is partially transparent or doesn't cover the entire screen).

Android managing fragments from activity elegantly

Description of what I'm trying to accomplish:
I have an app that uses a FragmentActivity with a LinearLayout as a container for the fragments. I click different buttons on the FragmentActivity UI and add and remove Fragments to the container in the FragmentActivity. In addition to clicking buttons on the FragmentActivity UI, each Fragment also has buttons that can be clicked which will remove the current fragment and add a different fragment in its place.
The Android way of doing things as I understand it:
I have been reading up on how to do this and as I understand it, the 'proper' way of doing things is to use the FragmentActivity as sort of a relay station and have each fragment do callbacks to the FragmentActivity to communicate events and deal with them.
Scenario:
So let's say that the FragmentActivity is displaying Fragment A and when the user clicks a button in FragmentA I want to stop showing FragmentA and start showing FragmentB. To do this I have created an interface in FragmentA called AListener. In the onAttach() method of FragmentA I use the suggested method of checking that the FragmentActivity implements AListener. When the button in FragmentA is clicked I use one of the callback methods from AListener to communicate the click event to the FragmentActivity. In the FragmentActivity I create an instance of FragmentB and add it to the container in FragmentActivity. Then if some event happens in FragmentB I use the same scheme to communicate the event to the FragmentActivity and do something interesting.
So what's the problem?
For my application I have found this scheme of having Fragments call back to the FragmentActivity and then having the FragmentActivity create a new fragment or call forward to and existing fragment very cumbersome. I have many fragments that need to be displayed by the FragmentActivity and therefore I am implementing an interface for every type of fragment that needs to be displayed (Each fragment is different so they each have their own interface). This causes clashes when I have two interfaces that have the same method signatures and I'm forced to rename one of the methods.
For instance, if I want to attach a listener to a fragment using the onAttach() method of the fragment, then my FragmentActivity must implement the interface. I have found several instances where I have callback methods that have the same name (or I'm forced to name them something similar but different because of a namespace collision). One solution to this would be to use an anonymous classes as callbacks instead of having the FragmentActivity implement the interface. This seems to work well enough, but goes against what the Android documentation says about using the onAttach() method to set the listener.
Are there any elegant ways to approach this problem? It seems to me the tradeoff is that you either force the FragmentActivity to implement an interface for each Fragment that you want to display in it and have the fun problem of watching out for method signature collisions, or you go against the Android documentation and use Anonymous classes to handle the callbacks (not sure of the implications of this).
I am fairly new to Java and feel like I could be missing a concept here that would solve my problem. Can anyone set me straight on how to solve this problem elegantly?
I completely understand your problem since i was dealing it for a long time. Here is the solution i came up right now! It may need some modification based on your need but i it works well.
first of all to to make communicating of event easier in your app use an EventBus! here is the most famous one https://goo.gl/nAEW6
event bus allows you to send event from anywhere to anywhere without need to worry about implementing interfaces, broadcast receivers, threading, etc.
Then add FragmentOrganizer to your app. It's a base class for all of your Fragment Organizers. basically you need one for each activity. Here is the code
public abstract class FragmentOrganizer {
protected FragmentManager fragmentManager;
public FragmentOrganizer(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
openFragment(getInitialFragment());
EventBus.getDefault().register(this);
}
protected abstract Fragment getInitialFragment();
protected abstract void onEvent(Object event);
public abstract boolean handleBackNavigation();
public void freeUpResources(){
EventBus.getDefault().unregister(this);
}
protected Fragment getOpenFragment(){
String tag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() -1).getName();
return fragmentManager.findFragmentByTag(tag);
}
protected boolean isFragmentOpen(Fragment fragment){
return isFragmentOpen(fragment, true);
}
protected boolean isFragmentOpen(Fragment fragment, boolean useArgs){
String fragmentTag = createFragmentTag(fragment, useArgs);
if (fragmentManager.getBackStackEntryCount() != 0) {
String name = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();
if(!useArgs)
name = name.substring(0, name.indexOf("-"));
return name.equals(fragmentTag);
}
return false;
}
private String createFragmentTag(Fragment fragment, boolean addArgs) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(fragment.getClass().getSimpleName());
if(addArgs) {
stringBuilder.append("-");
if (fragment.getArguments() != null)
stringBuilder.append(fragment.getArguments().toString());
}
return stringBuilder.toString();
}
public void openFragment(Fragment fragment) {
if(isFragmentOpen(fragment))
return;
String fragmentTag = createFragmentTag(fragment, true);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.activity_main_fragment_container, fragment, fragmentTag);
transaction.addToBackStack(fragmentTag).commit();
}
}
Now you need to create your fragment organizer that inherit from FragmentOrganizer and implements 3 required methods. here the sample
public class MainFragmentOrganizer extends FragmentOrganizer {
public MainFragmentOrganizer(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
protected Fragment getInitialFragment() {
return HomeFragment.newInstance();
}
#Override
public void onEvent(Object event){
if(event instanceof ClickedOnPhotoEvent){
String photoCode = ((ClickedOnPhotoEvent) event).photoCode;
openFragment(PhotoFragment.newInstance(photoCode));
}
}
#Override
public boolean handleBackNavigation(){
Fragment fragment = getOpenFragment();
if (fragment instanceof HomeFragment){
return false;
} else {
fragmentManager.popBackStack();
return true;
}
}
}
And in your activity you just need to insatiate your FragmentManager and let it do the magic!
fragmentManager = getSupportFragmentManager();
fragmentOrganizer = new MainFragmentOrganizer(getSupportFragmentManager());
#Override
public void onBackPressed() {
//first let fragment organizer handle back. If it does not activity takes cares of it!
if(!fragmentOrganizer.handleBackNavigation()){
finish();
}
}
#Override
protected void onDestroy() {
fragmentOrganizer.freeUpResources();
super.onDestroy();
}
It may seem a lot of code but as you see most of the code encapsulated in FragmentOrganizer base class and it does all the general works so you just have to copy this file from one project to another.
As i said in the beginning i just came up with this solution right now, so it may not be perfect. I Plan to use this in my next project i hope you do to. And if you do i really appritiate if you share your though. have a good time
A co-worker of mine came up with what I consider an elegant solution to this problem.
Remember, what we're trying to achieve is a way for fragments to callback to the parent activity without having the activity implement the interface. Also, we need to be able to automatically set the listener again if the activity is destroyed and then recreated.
Activities have a lifecycle callback called onAttachFragment(Fragment fragment) which is called whenever a fragment is being attached to the activity. So, for instance, when a new fragment is created within the activity, this gets called. It also gets called if an activity that was previously destroyed gets recreated. What you can do is use an interface or an anonymous class to set a listener on the new fragment in onAttachFragment like this:
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
//Determine which fragment this is by checking its tag
if(fragment.getTag().contains(TextFrag.FRAG_TAG)){
//set a listener on this fragment using an anonymous class or interface
((TextFrag)fragment).setListener(new TextFragButtonListener() {
#Override
public void onButtonClicked() {
count++;
counterTV.setText(String.valueOf(count));
}
});
}
}
Using this technique we are able to avoid the activity having to implement an interface for the callback and thus we avoid any naming conflicts with our callback methods. Also, if the activity is destroyed, once it is recreated the listener will be automatically reset so our callbacks will still work.
There are probably many other ways to do this and I'd love to here anyone's criticisms of this technique and suggestions for any other techniques.

PreferenceFragment doesn't get onActivityResult call from in app billing request

I have a preference screen that presents the user with a checkbox to disable ads. When the user clicks this for the first time, they are presented with an In App Billing purchase option to disable the ads.
The issue I'm facing here is that I can't see any way to get the onActivityResult callback into the fragment.
So I have a PreferenceActivity loading a PreferenceFragment (of which I can't seem to get a reference). In App Billing requires a call to startIntentSenderForResult, which Fragments don't have, only Activities.
When I launch the purchase flow with startIntentSenderForResult, the Activity's onActivityResult gets called, but I need this in the fragment.
Because I loaded the PreferenceFragment into the PreferenceActivity with the following, I don't think I can get a reference to the Fragment to pass the call down.
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.layout.preferences_headers, target);
}
#Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SyncPreferencesFragment.class.getName());
modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
What am I missing here? I dont' want to split up all of my purchase logic, so how do I get my Fragment to get the onActivityForResult call?
The original start request must come from the Fragment in order for Android to deliver the result back to the same Fragment. If the Activity starts the request, the result doesn't inherently get handed to every Fragment that may be attached to the manager. In addition to this, you have to ensure that if onActivityResult() is overridden in the top-level Activity, that you call super.onActivityResult() as well, or the message won't get delivered to the Fragment.
The problem with IAB is your given a PendingIntent instead of a standard Intent to fire, and there is no method on Fragment to trigger the initial action even if you can move your code there. To keep things as they are, you may have to do a bit of a swizzle. One thing you could do is make use of a custom interface inside the onAttach() method of your Fragment and use it to allow the Fragment to hand itself up to the Activity. Something like:
public class SyncPreferencesActivity extends PreferenceActivity
implements SyncPreferencesFragment.OnAttachCallback {
SyncPreferencesFragment mTargetFragment;
#Override
public void onAttachSyncFragment(SyncPreferencesFragment fragment) {
mTargetFragment = fragment;
}
}
...with some additions to the corresponding Fragment...
public class SyncPreferencesFragment extends PreferenceFragment {
public interface OnAttachCallback {
public void onAttachSyncFragment(SyncPreferencesFragment fragment);
}
#Override
public void onAttach(Activity activity) {
try {
OnAttachCallback callback = (OnAttachCallback) activity;
callback.onAttachSyncFragment(this);
} catch (ClassCastException e) {
throw new ClassCastException("You Forgot to Implement the Callback!");
}
}
}
With something like this, at least you have a reference to the instance so you can forward the result or anything else that might be necessary. If you want to be super clean, you could also implement a matching "detach" that clears the reference in the Activity.
Here is a simpler way that I am using to get a reference to a Preference Fragment from a Preference Activity:
private WeakReference<GeneralPreferenceFragment> mGeneralFragment;
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
// "id" might be arbitrary and "tag" can be null
Log.d(TAG, "onAttachFragment: "+fragment.getId()+","+fragment.getTag());
// however class can be used to identify different fragments
Log.d(TAG, "onAttachFragment: "+fragment.getClass()+","+(fragment instanceof GeneralPreferenceFragment));
if(fragment instanceof GeneralPreferenceFragment) {
mGeneralFragment=new WeakReference<>((GeneralPreferenceFragment)fragment);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Fragment f=mGeneralFragment.get();
if(f!=null) {
// can now use findPreference, etc
}
}
This method is convenient because it doesn't require the use of interfaces or modifications to the fragment classes.

Categories

Resources