📜 ⬆️ ⬇️

Implementing ProgressDialog and AsyncTaskLoader on Fragments

On Habré, a good article has already been published which describes the joint use of ProgressDialog and AsyncTask, here I will describe how to achieve similar functionality but on fragments, more precisely using DialogFragment and AsyncTaskLoader .

So, the goal:

image

Implementation
1. Send a message from the asynchronous process to the abstract class AbstractTaskLoader, a successor from AsyncTaskLoader:
public abstract class AbstractTaskLoader extends AsyncTaskLoader<Object> { //type of the published values public static int MSGCODE_PROGRESS = 1; public static int MSGCODE_MESSAGE = 2; private Handler handler; protected AbstractTaskLoader(Context context) { super(context); } protected void setHandler(Handler handler){ this.handler = handler; } /** * Helper to publish string value * @param value */ protected void publishMessage(String value){ if(handler!=null){ Bundle data = new Bundle(); data.putString("message", value); /* Creating a message */ Message msg = new Message(); msg.setData(data); msg.what = MSGCODE_MESSAGE; /* Sending the message */ handler.sendMessage(msg); } } … 

Further, our class, which performs some kind of long-lasting operation, is already inherited from this abstract class and can inform about the process of performing the task:
 @Override public Object loadInBackground() { … publishMessage(result); … } 

2. The next step is the implementation of the ProgressDialog that displays the message and is responsible for correctly starting / restarting our Loader when changing the orientation of the screen. The class also includes callback handlers for the Loader and dialog:
 public abstract class AbstractTaskProgressDialogFragment extends DialogFragment implements LoaderCallbacks<Object>, OnCancelListener { private ProgressDialog progressDialog; private final AbstractTaskLoader loader; protected abstract void onLoadComplete(Object data); protected abstract void onCancelLoad(); protected AbstractTaskProgressDialogFragment(AbstractTaskLoader loader,String message){ loader.setHandler(handler); this.loader = loader; Bundle args = new Bundle(); args.putString("message", message); setArguments(args); } @Override public ProgressDialog onCreateDialog(Bundle savedInstanceState) { String message = getArguments().getString("message"); progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage(message); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setOnCancelListener(this); progressDialog.show(); return progressDialog; } private final Handler handler = new Handler() { public void handleMessage(Message msg) { if(msg.what == AbstractTaskLoader.MSGCODE_MESSAGE){ String message = AbstractTaskLoader.getMessageValue(msg); if(message!=null){ //update progress bar message if(progressDialog!=null){ progressDialog.setMessage(message); } } } } }; ... 

The class is also made abstract, however it is not necessary, just one of the options for implementing the smallest binding. Only the results of the work are important:
onLoadComplete (Object data);
onCancelLoad ();
and the implementation of these methods in the successor (I did it through the interface, see below).

Here the Loader is transferred to the constructor, which will be executed and the test message displayed immediately upon launch. Handler is used to send information messages from the Loader about progress. In onCreateDialog we create and show our dialogue.
')
Next, we need to indicate that when the screen orientation changes, this fragment retains its state. This is done using the setRetainInstance (true) method; in this case, onDestroy will not be called when the screen orientation changes and the dialog does not close.
 @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, loader.getArguments(), this); } 

We override the onActivityCreated method, in which we also launch our Loader on the first launch or reconnect to the existing one (when the screen orientation changes).

A small digression:
The setRetainInstance method does not quite work correctly for a DialogFragment paired with a “compatibility library” ( compatibility-library ), that is, the onDestroy method is still called though it should not. But there is a workaround, in our dialogue we add:
 @Override public void onDestroyView() { if (getDialog() != null && getRetainInstance()) getDialog().setOnDismissListener(null); super.onDestroyView(); } 

Details here .

Loader startup and shutdown processing:
 @Override public Loader<Object> onCreateLoader(int id, Bundle args) { loader.forceLoad(); return loader; } @Override public void onLoadFinished(Loader<Object> loader, Object data) { onLoadComplete(data); ((AbstractTaskLoader) loader).setHandler(null); hideDialog(); } 

Processing cancellation by a user - pressing the “Back” button:
 @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); loader.cancelLoad(); //base metod loader.setCanseled(true); //custom flag onCancelLoad(); //do not invoke dismiss for this dialog } 

3. The base frame is created, now use it.

Create an interface for notification of completion:
 public interface ITaskLoaderListener { void onLoadFinished(Object data); void onCancelLoad(); } 

We create our implementation of ProgressDialog on the basis of the created abstract class:
 public class TaskProgressDialogFragment extends AbstractTaskProgressDialogFragment { private ITaskLoaderListener taskLoaderListener; private final Handler handler = new Handler(); protected TaskProgressDialogFragment(AbstractTaskLoader loader, String message) { super(loader, message); } protected void setTaskLoaderListener(ITaskLoaderListener taskLoaderListener){ this.taskLoaderListener = taskLoaderListener; } @Override protected void onLoadComplete(final Object data) { if(taskLoaderListener!=null){ taskLoaderListener.onLoadFinished(data); } } @Override protected void onCancelLoad() { if (taskLoaderListener != null) { taskLoaderListener.onCancelLoad(); } } ... 

And Builder for easy building and running Loader-a:
 public static class Builder { FragmentActivity fa; AbstractTaskLoader loader; ITaskLoaderListener taskLoaderListener; Boolean cancelable; String progressMsg; public Builder(FragmentActivity fa, AbstractTaskLoader loader, String progressMsg) { this.fa = fa; this.loader = loader; this.progressMsg = progressMsg; } public Builder setTaskLoaderListener( ITaskLoaderListener taskLoaderListener) { this.taskLoaderListener = taskLoaderListener; return this; } public Builder setCancelable(Boolean cancelable) { this.cancelable = cancelable; return this; } public void show() { String TAG_FRAGMENT = UUID.randomUUID().toString(); // remove prev if exists FragmentManager fm = fa.getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment prev = fm.findFragmentByTag(TAG_FRAGMENT); if (prev != null) { ft.remove(prev); } // create dlg fragment TaskProgressDialogFragment fragment = new TaskProgressDialogFragment( loader, progressMsg); fragment.setTaskLoaderListener(taskLoaderListener); fragment.setCancelable(cancelable); Bundle args = new Bundle(); args.putString("message", progressMsg); fragment.setArguments(args); // show the dialog. fragment.show(ft, TAG_FRAGMENT); } } 

4. Last, this is our Loader. For example, just every second updates the test message in ProgreesDialog-e:
 public class SampleTask extends AbstractTaskLoader { @Override public Object loadInBackground() { String result = ""; for(int i=0; i<10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = "Wait: " + String.valueOf(i); publishMessage(result); if(isCanselled()){ break; } Log.d(TAG, result); } return result; } 

Add a static method for easy launch:
 public static void execute(FragmentActivity fa, ITaskLoaderListener taskLoaderListener) { SampleTask loader = new SampleTask(fa); new TaskProgressDialogFragment.Builder(fa, loader, "Wait...") .setCancelable(true) .setTaskLoaderListener(taskLoaderListener) .show(); } 

5. Call
 public class SampleActivity extends FragmentActivity implements ITaskLoaderListener{ … SampleTask.execute(this, this); @Override public void onCancelLoad() { Log.d(TAG, "task canceled"); } @Override public void onLoadFinished(Object data) { if(data!=null && data instanceof String){ Log.d(TAG, "task result: " + data); } } 

Download the source code of the example, constructive criticism and comments are welcome.

Sources:
1. Evelina Vrabie Blog, stingy 'Android Fragments, Saving State and Screen Rotation'
2. Stack Overflow

Source: https://habr.com/ru/post/131560/


All Articles