3

There is some questions already close to this question but they haven't been very helpful for me. So here comes a new one.

I have an Activity which has two tabs. Each tab contains a ListFragment (SherlockListFragment to be exact). One tab shows a list of shopping list objects and the other shows a list of recipe objects. Now I want to create a DialogFragment for renaming a list or a recipe or any other object I might later add to the application.

The solution provided here sounded promising but because ListFragment can not be registered to listen clicks from the dialog I should make my Activity to listen them which is not ideal because then my Fragments would not be independent. How to get data out of a general-purpose dialog class

Ideally I would like to have my rename dialog as independent and reusable as possible. This far I have invented just one way to do this. Sending the objects className and id to the dialog and then using switch case to fetch the correct object from the database. This way the dialog would be able to update the objects name by itself (if the object has rename method). But the requery to the database sounds just dump because the ListFragment has the object already. And then the dialog would need a new case in the switch for each new kind of object.

Any ideas?

Community
  • 1
  • 1
Mika
  • 1,419
  • 18
  • 37
  • Could one solution be using something like this `Object o = ((SherlockListFragment) getTargetFragment()).getListView().getAdapter().getItem(getArguments().getInt("position"));` Now I just would need to make my shopping list and recipe classes to be subclasses of some abstract class which would define the rename() method and cast the object to that. – Mika Jun 26 '13 at 11:40
  • ListFragment can be registered to listen for clicks. When you create a new DialogFragment eg. dialogFragment, you can set a reference to that ListFragment by dialogFragment.setTargetFragment(this) then in your DialogFragment onClick() method, Fragment myListFragment = (MyListFragmentName) getTargetFragment; This then allows you to invoke any ListFragment methods you may have eg. myListFragment.specificMethod(); – mgibson Jul 02 '13 at 08:25
  • It is close, but because list objects and recipe objects are in their own ListFragments (RecipeListFragment and ListListFragment) I would need two implementations for getting the target fragment. And then the dialog would not be independent from the ListFragment. – Mika Jul 02 '13 at 10:53
  • You could have both of those ListFragment inhereting from a ListFragment superclass containing a method to be overridden by each subclass. Then you could get cast the target fragment to the superclass and call that method. I have used this several times :) – mgibson Jul 02 '13 at 11:00
  • I ended up creating a new class Model and subclass my ShoppingList and Recipe from that and made them implement Serializable. Model class has methods getName() and rename() which are overriden in the subclasses. Now I can pass the serialized object to the DialogFragment and "open" it as a Model in the DialogFragment. This allows me to get the current name of the shopping list or recipe and rename them within the DialogFragment. No need to couple the dialog with its origin. – Mika Jul 02 '13 at 22:35
  • Sounds good, glad you got it sorted. Fair point on the no need to couple with its origin. When I was doing that I was always wanting feedback to the previous Fragment. I guess that isn't very relevant to what you are doing – mgibson Jul 03 '13 at 08:43

2 Answers2

2

I actually just created a similar sort of dialog fragment to what you're asking about. I was for a fairly large app and it was getting kind of ridiculous the amount of dialog listeners our main activity was extending just to listen for the results of a single dialog.

In order to make something a bit more flexible I turned to using ListenableFuture from Google's Guava concurrent library.

I created the following abstract class to use:

public abstract class ListenableDialogFragment<T> extends DialogFragment implements ListenableFuture<T> {

private SettableFuture<T> _settableFuture;

public ListenableDialogFragment() {
    _settableFuture = SettableFuture.create();
}

@Override
public void addListener(Runnable runnable, Executor executor) {
    _settableFuture.addListener(runnable, executor);
}

@Override
public boolean cancel(boolean mayInterruptIfRunning) {
    return _settableFuture.cancel(mayInterruptIfRunning);
}

@Override
public boolean isCancelled() {
    return _settableFuture.isCancelled();
}

@Override
public boolean isDone() {
    return _settableFuture.isDone();
}

@Override
public T get() throws InterruptedException, ExecutionException {
    return _settableFuture.get();
}

@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    return _settableFuture.get(timeout, unit);
}

public void set(T value) {
    _settableFuture.set(value);
}

public void setException(Throwable throwable) {
    _settableFuture.setException(throwable);
}

// Resets the Future so that it can be provided to another call back
public void reset() {
    _settableFuture = SettableFuture.create();
}

@Override
public void onDismiss(DialogInterface dialog) {
    // Cancel the future here in case the user cancels our of the dialog
    cancel(true);
    super.onDismiss(dialog);
}

Using this class I'm able to create my own custom dialog fragments and use them like this:

            ListenableDialogFragment<int> dialog = GetIdDialog.newInstance(provider.getIds());

    Futures.addCallback(dialog, new FutureCallback<int>() {
        @Override
        public void onSuccess(int id) {
            processId(id);
        }

        @Override
        public void onFailure(Throwable throwable) {
            if (throwable instanceof CancellationException) {
                // Task was cancelled
            }

            processException(throwable);
        }
    });

This is where GetIdDialog is a custom instance of a ListenableDialogFragment. I can reuse this same dialog instance if needs be by simply calling dialog.reset in the onSuccess and onFailure methods to ensure that the internal Future gets reloaded for adding back to a callback.

I hope this helps you out.

Edit: Sorry forgot to add, in your dialog you can implement an on click listener that does something like this to trigger the future:

private class SingleChoiceListener implements DialogInterface.OnClickListener {
    @Override
    public void onClick(DialogInterface dialog, int item) {
        int id = _ids[item];

        // This call will trigger the future to fire
        set(id);
        dismiss();
    }
}
  • This would eliminate the need of listening the response in the Activity, but if I understood correctly you are defining what to do with the response in the Activity/Fragment that fired the dialog? I would like to make the dialog to handle the response by it self. – Mika Jul 01 '13 at 22:52
  • You are correct, whatever creates the dialog needs to consume the result and decide what to do with it. Futures.addCallback(dialog, new FutureCallback { onSuccess(T result) { //deal with result here } onFailure(Throwable t) { // deal with exception here } }); is the call that needs to be used. – Chris Forsyth Jul 09 '13 at 19:21
1

I would maybe just using a static factory pattern of some variation to allow dynamic initialization of the DialogFragment.

private enum Operation {ADD, EDIT, DELETE}

private String title;
private Operation operation;

public static MyDialogFragment newInstance(String title, Operation operation)
{
    MyDialogFragment dialogFragment = new DialogFragment();
    dialogFragment.title = title;   // Dynamic title
    dialogFragment.operation = operation;

    return dialogFragment;
}

Or.. and I would recommend this more, have a static factory method for each type of operation you will use it for. This allows different dynamic variations to be more concrete and ensures that everything works together. This also allows for informative constructors.

Eg.

public static MyDialogFragment newAddItemInstance(String title)
{
    MyDialogFragment dialogFragment = new DialogFragment();
    dialogFragment.title = title;   // Dynamic title

    return dialogFragment;
}

public static MyDialogFragment newEditItemInstance(String title)
{
    MyDialogFragment dialogFragment = new DialogFragment();
    dialogFragment.title = title;   // Dynamic title

    return dialogFragment;
}

And then of course create an interface that every calling Activity / Fragment (in which case you need to set this Fragment as the targetFragment and get reference to that target Fragment in your DialogFragment) so that the implementation is taken care of in the target Fragment and nothing to do with the DialogFragment.

Summary: There are various ways of going about this, for simplicity, I would stick with some form of static factory pattern and make clever use of interfaces to separate any the logic from the DialogFragment hence making it more reusable

EDIT: From your comment I would suggest you look at two things:

  • Target Fragments (See the comment I made on your question). You can invoke methods in your ListFragment from your DialogFragment.

  • Strategy Pattern. How does the Strategy Pattern work?. This allows you to perform the same operation (with various tailored implementation for each type) on different objects. Very useful pattern.

Community
  • 1
  • 1
mgibson
  • 6,103
  • 4
  • 34
  • 49
  • If I understood you correctly with this method you are able to do multiple different kind of operations to one kind of object. But I was asking for a method how to do one kind of operation to multiple different object. – Mika Jul 01 '13 at 22:54
  • I am wondering whether you can connect the dialog fragment to the activity's view model – Gilbert May 04 '20 at 14:07