Mika
Mika

Reputation: 1479

How to desing a reusable DialogFragment

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?

Upvotes: 3

Views: 2789

Answers (2)

mgibson
mgibson

Reputation: 6213

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.

Upvotes: 1

Chris Forsyth
Chris Forsyth

Reputation: 67

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();
    }
}

Upvotes: 2

Related Questions