Skip to content
This repository was archived by the owner on Dec 17, 2020. It is now read-only.

Commit dc399c6

Browse files
committed
Major update:
- Renamed TaskHandler to TaskManager; - Some critical bug fixes; - Added the TaskAttachListener and corresponding attachListener(String, TaskAttachListener) method, for restoring the UI state prior to attaching a Tasks Callback listener; - Added some more documentation (still work-in-progress though);
1 parent bc3607e commit dc399c6

File tree

11 files changed

+258
-119
lines changed

11 files changed

+258
-119
lines changed

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ dependencies {
2525
In order to, execute a task which modifies the user-interface and to retain it across configuration changes you will need to do three things:
2626

2727
1. Create an implementation of the `Task` class;
28-
2. Execute the task using the `TaskHandler` in an`Activity` which implements the `Callback` interface;
29-
3. Retain the task when configuration changes by overriding the `onStart()` method and calling the `TaskHandler.attachListener()` method;
28+
2. Execute the task using the `TaskManager` in an`Activity` which implements the `Callback` interface;
29+
3. Retain the task when configuration changes by overriding the `onStart()` method and calling the `TaskManager.attachListener()` method;
3030

31-
The Activity's `TaskHandler` makes sure that tasks can be retained across configuration changes and is responsible for removing the `Callback` listener when the Activity's user-interface is no longer valid. You are however responsible for re-attaching a new `Callback` listener when the Activity 's user-interface is restarted.
31+
The Activity's `TaskManager` makes sure that tasks can be retained across configuration changes and is responsible for removing the `Callback` listener when the Activity's user-interface is no longer valid. You are however responsible for re-attaching a new `Callback` listener when the Activity 's user-interface is restarted.
3232

3333
### 1.1 Creating the task
3434

@@ -61,23 +61,23 @@ private class ExampleTask extends Task<Integer, String> {
6161

6262

6363
### 1.2 Executing the task
64-
Before you can execute the `ExampleTask` you first need to get the current Activity's `TaskHandler`. A `TaskHandler` keeps references to tasks and executes them. You can obtain the current Activity's `TaskHandler` using the `TaskHandler.getActivityTaskHandler()` method:
64+
Before you can execute the `ExampleTask` you first need to get the current Activity's `TaskManager`. A `TaskManager` keeps references to tasks and executes them. You can obtain the current Activity's `TaskManager` using the `TaskManager.getActivityTaskManager()` method:
6565

6666
```java
67-
public TaskHandler getTaskHandler(){
68-
return TaskHandler.getActivityTaskHandler(getSupportFragmentManager());
67+
public TaskManager getTaskManager(){
68+
return TaskManager.getActivityTaskManager(getSupportFragmentManager());
6969
}
7070
```
7171

72-
Then you can execute the task using the `TaskHandler.execute()` method. This method needs two arguments, the task to execute and a `Callback` listener to send feedback to. Preferably your activity implements the `Callback` interface, but this isn't necessarily needed.
72+
Then you can execute the task using the `TaskManager.execute()` method. This method needs two arguments, the task to execute and a `Callback` listener to send feedback to. Preferably your activity implements the `Callback` interface, but this isn't necessarily needed.
7373

7474
```java
7575
public class Main extends AppCompatActivity implements Task.Callback {
7676

7777
@Override
7878
public void onClick(View view){
7979
ExampleTask task = new ExampleTask("activity-unique-tag");
80-
getTaskHandler().execute(task, this);
80+
getTaskManager().execute(task, this);
8181
}
8282

8383
@Override
@@ -94,30 +94,30 @@ public class Main extends AppCompatActivity implements Task.Callback {
9494
```
9595

9696
### 1.2 Retaining the task
97-
When the configuration changes (device rotates) the Activity's `TaskHandler` will automatically remove the `Callback` listeners from all active tasks. This is needed otherwise the tasks could leak Activity, Fragment or other references.
97+
When the configuration changes (device rotates) the Activity's `TaskManager` will automatically remove the `Callback` listeners from all active tasks. This is needed otherwise the tasks could leak Activity, Fragment or other references.
9898

9999
> **In-depth:**
100-
> The `TaskHandler` will automatically remove the `Callback` listeners when the Activity is stopping (`onStop()`). At this moment the user-interface has become *"unstable"*, for example, when this happens the FragmentManager refuses to add new Fragments because the Activity's `onSaveInstanceState()` method has already been called. If the `Callback` listener is not removed by the `TaskHandler` before this point, then a `DialogFragment.show()` call will throw an exception when called in the `onPostExecute()` method. This is why the `Callback` listeners are removed when the Activity stops.
100+
> The `TaskManager` will automatically remove the `Callback` listeners when the Activity is stopping (`onStop()`). At this moment the user-interface has become *"unstable"*, for example, when this happens the FragmentManager refuses to add new Fragments because the Activity's `onSaveInstanceState()` method has already been called. If the `Callback` listener is not removed by the `TaskManager` before this point, then a `DialogFragment.show()` call will throw an exception when called in the `onPostExecute()` method. This is why the `Callback` listeners are removed when the Activity stops.
101101
102-
To retain the task when your Activity is recreated you will need to re-attach a (new) `Callback` listener using the `TaskHandler.attachListener()` method. The best place to do this is in the `onStart()` method right after the user-interface has been created.
102+
To retain the task when your Activity is recreated you will need to re-attach a (new) `Callback` listener using the `TaskManager.attachListener()` method. The best place to do this is in the `onStart()` method right after the user-interface has been created.
103103

104104

105105
```java
106106
@Override
107107
public void onStart(){
108108
super.onStart();
109109
//Re-attach the this Activity as listener for task
110-
getTaskHandler().attachListener("activity-unique-tag", this);
110+
getTaskManager().attachListener("activity-unique-tag", this);
111111
}
112112
```
113113

114114
## 2. How it works
115115
---
116116
####**Retaining tasks**
117-
Tasks retained using the described method are stored in a *"no-ui-fragment"* this fragment retained across configuration changes and is added to your Activity's `FragmentManager` the first time you call `TaskHandler.getActivityTaskHandler()`. This fragment is from that point on bound to the Activity's life-cycle and holds an internal `TaskHandler`. The fragment makes sure that the internal `TaskHandler` removes all `Callback` listeners as soon as the Activity is stopping (`onStop()`).
117+
Tasks retained using the described method are stored in a *"no-ui-fragment"* this fragment retained across configuration changes and is added to your Activity's `FragmentManager` the first time you call `TaskManager.getActivityTaskManager()`. This fragment is from that point on bound to the Activity's life-cycle and holds an internal `TaskManager`. The fragment makes sure that the internal `TaskManager` removes all `Callback` listeners as soon as the Activity is stopping (`onStop()`).
118118

119119
####**Task without callback finishes**
120-
When a Task doesn't have a `Callback` listener to deliver it's results to it will skip the delivery and redeliver the results as soon as a new listener is attached. If you call the `TaskHandler.attachListener()` method in the `onStart()` method, then the listener will be fired and you need to be sure that the user-interface is ready.
120+
When a Task doesn't have a `Callback` listener to deliver it's results to it will skip the delivery and redeliver the results as soon as a new listener is attached. If you call the `TaskManager.attachListener()` method in the `onStart()` method, then the listener will be fired and you need to be sure that the user-interface is ready.
121121

122122
Only the `onPostExecute()` and `onCanceled()` methods will be redelivered, other methodes won't be redelivered.
123123

@@ -155,7 +155,7 @@ To get the tasks most recent progress update use the `getLastKnownProgress()` me
155155
If you need the `onProgressUpdated` and `onCanceled` callback methods you can implement the `AdvancedCallback` interface, which is an extension of the `Callback` interface.
156156

157157
####**TaskExecutor & Executor**
158-
You can also execute tasks without using a `TaskHandler` this means that you are responsible for removing and setting the `Callback` listener. Executing tasks without using the `TaskHandler` is handy when you don't need to get any feedback to the Activity's user-interface.
158+
You can also execute tasks without using a `TaskManager` this means that you are responsible for removing and setting the `Callback` listener. Executing tasks without using the `TaskManager` is handy when you don't need to get any feedback to the Activity's user-interface.
159159

160160
```java
161161
TaskExecutor.executeParallel(new ExampleTask());

demo/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ android {
2020
}
2121

2222
dependencies {
23-
//compile project(':library')
24-
compile 'org.neotech.library:android-retainable-tasks:0.1.0'
23+
compile project(':library')
24+
//compile 'org.neotech.library:android-retainable-tasks:0.1.0'
2525

2626
compile 'com.android.support:appcompat-v7:23.2.0'
2727
compile 'com.android.support:design:23.2.0'
Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.neotech.app.retainabletasksdemo;
22

3+
import android.annotation.SuppressLint;
34
import android.content.Intent;
45
import android.net.Uri;
56
import android.os.Bundle;
@@ -9,21 +10,26 @@
910
import android.support.v7.app.AppCompatActivity;
1011
import android.support.v7.widget.Toolbar;
1112
import android.view.View;
13+
import android.widget.Button;
1214
import android.widget.Toast;
1315

16+
import org.neotech.app.retainabletasksdemo.tasks.CountDownTask;
1417
import org.neotech.app.retainabletasksdemo.tasks.SimpleTask;
1518
import org.neotech.app.retainabletasksdemo.tasks.TaskWithoutCallback;
1619
import org.neotech.library.retainabletasks.Task;
1720
import org.neotech.library.retainabletasks.TaskExecutor;
18-
import org.neotech.library.retainabletasks.TaskHandler;
21+
import org.neotech.library.retainabletasks.TaskManager;
1922

2023
public class Main extends AppCompatActivity implements View.OnClickListener, Task.AdvancedCallback, OnAlertDialogClickListener {
2124

22-
private static final String TASK_PROGRESS = "Demo-task";
25+
private static final String TASK_RETAIN_UI_STATE = "retain-ui-state";
26+
private static final String TASK_PROGRESS = "progress-dialog";
2327
private static final String DIALOG_PROGRESS = "progress-dialog";
2428

2529
private ProgressDialog progressDialog;
2630

31+
private Button retainUserInterfaceButton;
32+
2733
@Override
2834
protected void onCreate(Bundle savedInstanceState) {
2935
super.onCreate(savedInstanceState);
@@ -33,13 +39,34 @@ protected void onCreate(Bundle savedInstanceState) {
3339
findViewById(R.id.fab).setOnClickListener(this);
3440
findViewById(R.id.button_no_ui_task).setOnClickListener(this);
3541
findViewById(R.id.button_progress_task).setOnClickListener(this);
42+
43+
retainUserInterfaceButton = (Button) findViewById(R.id.button_retain_ui_state_task);
44+
retainUserInterfaceButton.setOnClickListener(this);
3645
}
3746

3847
@Override
3948
protected void onStart() {
4049
super.onStart();
4150
progressDialog = ProgressDialog.getExistingInstance(getSupportFragmentManager(), DIALOG_PROGRESS);
42-
getTaskHandler().attachListener(TASK_PROGRESS, this);
51+
getTaskManager().attachListener(TASK_PROGRESS, this);
52+
53+
54+
getTaskManager().attachListener(TASK_RETAIN_UI_STATE, new TaskManager.TaskAttachListener() {
55+
@Override
56+
public Task.Callback onPreAttach(Task<?, ?> task) {
57+
/**
58+
* the onPreAttach method will only be called if the task did not deliver its result
59+
* and thus is still available/referenced by the TaskManger.
60+
*
61+
* At this point the UI can be restored to the "task is running" state.
62+
*/
63+
if (!task.isResultDelivered()) { //This call isn't necessary.
64+
retainUserInterfaceButton.setEnabled(false);
65+
retainUserInterfaceButton.setText("" + task.getLastKnownProgress());
66+
}
67+
return Main.this;
68+
}
69+
});
4370
}
4471

4572
@Override
@@ -48,47 +75,63 @@ public void onClick(View v) {
4875
if(id == R.id.fab){
4976
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/NeoTech-Software/Android-Retainable-Tasks")));
5077
} else if(id == R.id.button_progress_task) {
51-
if (getTaskHandler().isRunning(TASK_PROGRESS)) {
78+
if (getTaskManager().isRunning(TASK_PROGRESS)) {
5279
Toast.makeText(this, R.string.toast_task_already_running, Toast.LENGTH_SHORT).show();
5380
}
5481
SimpleTask task = new SimpleTask(TASK_PROGRESS);
55-
getTaskHandler().execute(task, this);
82+
getTaskManager().execute(task, this);
5683

5784
} else if(id == R.id.button_no_ui_task){
5885
TaskWithoutCallback task = new TaskWithoutCallback(this);
5986
TaskExecutor.execute(task);
60-
87+
} else if(id == R.id.button_retain_ui_state_task){
88+
CountDownTask task = new CountDownTask(TASK_RETAIN_UI_STATE, 10);
89+
getTaskManager().execute(task, this);
90+
retainUserInterfaceButton.setEnabled(false);
6191
}
6292
}
6393

64-
public TaskHandler getTaskHandler(){
65-
return TaskHandler.getActivityTaskHandler(getSupportFragmentManager());
94+
public TaskManager getTaskManager(){
95+
return TaskManager.getActivityTaskManager(getSupportFragmentManager());
6696
}
6797

6898
@Override
6999
public void onPreExecute(Task<?, ?> task) {
70-
progressDialog = ProgressDialog.showIfNotShowing(getSupportFragmentManager(), DIALOG_PROGRESS);
100+
if(task.getTag().equals(TASK_PROGRESS)) {
101+
progressDialog = ProgressDialog.showIfNotShowing(getSupportFragmentManager(), DIALOG_PROGRESS);
102+
}
71103
}
72104

73105
@Override
74106
public void onPostExecute(Task<?, ?> task) {
75-
progressDialog.dismiss();
76-
Snackbar.make(findViewById(android.R.id.content), getString(R.string.toast_task_finished, getString(R.string.task_progress_dialog)), Snackbar.LENGTH_LONG).show();
107+
if(task.getTag().equals(TASK_PROGRESS)) {
108+
progressDialog.dismiss();
109+
Snackbar.make(findViewById(android.R.id.content), getString(R.string.toast_task_finished, getString(R.string.task_progress_dialog)), Snackbar.LENGTH_LONG).show();
110+
} else if(task.getTag().equals(TASK_RETAIN_UI_STATE)){
111+
retainUserInterfaceButton.setEnabled(true);
112+
retainUserInterfaceButton.setText(R.string.task_retain_ui_state);
113+
}
77114
}
78115

79116
@Override
80117
public void onCanceled(Task<?, ?> task) {
81-
progressDialog.dismiss();
82-
Snackbar.make(findViewById(android.R.id.content), getString(R.string.toast_task_canceled, getString(R.string.task_progress_dialog)), Snackbar.LENGTH_LONG).show();
118+
if(task.getTag().equals(TASK_PROGRESS)) {
119+
progressDialog.dismiss();
120+
Snackbar.make(findViewById(android.R.id.content), getString(R.string.toast_task_canceled, getString(R.string.task_progress_dialog)), Snackbar.LENGTH_LONG).show();
121+
}
83122
}
84123

85124
@Override
86125
public void onProgressUpdate(Task<?, ?> task, Object progress) {
87-
progressDialog.setProgress((int) progress);
126+
if(task.getTag().equals(TASK_PROGRESS)) {
127+
progressDialog.setProgress((int) progress);
128+
} else if(task.getTag().equals(TASK_RETAIN_UI_STATE)){
129+
retainUserInterfaceButton.setText("" + (int) progress);
130+
}
88131
}
89132

90133
@Override
91134
public void onDialogFragmentClick(DialogFragment fragment, int which) {
92-
getTaskHandler().cancel(TASK_PROGRESS);
135+
getTaskManager().cancel(TASK_PROGRESS);
93136
}
94137
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.neotech.app.retainabletasksdemo.tasks;
2+
3+
import android.os.SystemClock;
4+
5+
import org.neotech.library.retainabletasks.Task;
6+
7+
/**
8+
* Created by Rolf on 3-3-2016.
9+
*/
10+
public class CountDownTask extends Task<Integer, Void> {
11+
12+
private final int amount;
13+
14+
public CountDownTask(String tag, int amount) {
15+
super(tag);
16+
this.amount = amount;
17+
}
18+
19+
@Override
20+
protected Void doInBackground() {
21+
for(int i = amount; i > 0; i--){
22+
publishProgress(i);
23+
SystemClock.sleep(1000); //Inaccurate count-down, but hey... its an example :)
24+
}
25+
return null;
26+
}
27+
}

demo/src/main/res/layout/content_main.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,11 @@
3030
android:layout_width="wrap_content"
3131
android:text="@string/task_progress_dialog" />
3232

33+
<Button
34+
android:id="@+id/button_retain_ui_state_task"
35+
android:layout_height="wrap_content"
36+
android:layout_width="wrap_content"
37+
android:text="@string/task_retain_ui_state" />
38+
3339
</LinearLayout>
3440
</ScrollView>

demo/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
<string name="task_no_ui_callback">Task without UI callback</string>
1010
<string name="task_progress_dialog">Progress dialog task</string>
11+
<string name="task_retain_ui_state">Retain UI state task</string>
12+
1113
<string name="dialog_progress_title">Loading…</string>
1214
<string name="action_cancel">Cancel</string>
1315
</resources>

library/src/main/java/org/neotech/library/retainabletasks/BaseTaskHandler.java renamed to library/src/main/java/org/neotech/library/retainabletasks/BaseTaskManager.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
/**
1111
* Created by Rolf on 29-2-2016.
1212
*/
13-
public class BaseTaskHandler extends TaskHandler {
13+
public class BaseTaskManager extends TaskManager {
1414

15-
private static final String TAG = "SimpleTaskHandler";
15+
private static final String TAG = "SimpleTaskManager";
1616

1717
protected final HashMap<String, Task<?, ?>> tasks = new HashMap<>();
1818

@@ -24,18 +24,37 @@ public class BaseTaskHandler extends TaskHandler {
2424
@Override
2525
@MainThread
2626
public Task<?, ?> attachListener(@NonNull String tag, @NonNull Task.Callback callback){
27-
Task<?, ?> task = tasks.get(tag);
27+
final Task<?, ?> task = tasks.get(tag);
2828
if(task == null){
2929
return null;
3030
}
3131
task.setCallback(new CallbackShadow(callback));
3232
return task;
3333
}
3434

35+
@Override
36+
@MainThread
37+
public Task<?, ?> attachListener(@NonNull String tag, @NonNull TaskAttachListener listener){
38+
final Task<?, ?> task = tasks.get(tag);
39+
if(task == null){
40+
return null;
41+
}
42+
task.setCallback(new CallbackShadow(listener.onPreAttach(task)));
43+
return task;
44+
}
45+
46+
@Override
47+
@MainThread
48+
public Task<?, ?> attachListener(@NonNull Task<?, ?> task, @NonNull Task.Callback callback) {
49+
task.setCallback(new CallbackShadow(callback));
50+
return task;
51+
}
52+
3553
@Override
3654
@MainThread
3755
public <Progress, Result> void execute(@NonNull Task<Progress, Result> task, @NonNull Task.Callback callback){
38-
if(tasks.get(task.getTag()) != null){
56+
final Task currentTask = tasks.get(task.getTag());
57+
if(currentTask != null && currentTask.isRunning()){
3958
throw new IllegalStateException("Task with an equal tag: '" + task.getTag() + "' has already been added and is currently running or finishing.");
4059
}
4160
tasks.put(task.getTag(), task);
@@ -45,11 +64,19 @@ public <Progress, Result> void execute(@NonNull Task<Progress, Result> task, @No
4564

4665
@Override
4766
@MainThread
48-
public boolean isRunning(String tag) {
67+
public boolean isResultDelivered(@NonNull String tag) {
68+
Task task = tasks.get(tag);
69+
return task != null && task.isResultDelivered();
70+
}
71+
72+
@Override
73+
@MainThread
74+
public boolean isRunning(@NonNull String tag) {
4975
Task task = tasks.get(tag);
5076
return task != null && task.isRunning();
5177
}
5278

79+
5380
@Override
5481
@MainThread
5582
public Task<?, ?> cancel(@NonNull String tag){

0 commit comments

Comments
 (0)