Last active
May 24, 2017 11:36
-
-
Save ultraon/895b5c2403f28c43ffbed7fd729a3812 to your computer and use it in GitHub Desktop.
Special class that can handle {@link Runnable} actions that started after {@link android.app.Activity#onPause()}, in this case state handler remembers all actions and starts them after {@link android.app.Activity#onResume()}. All methods should be called from UI thread otherwise an exception will be thrown. This class has very convenient usage w…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Handler; | |
import android.os.Looper; | |
import android.support.annotation.NonNull; | |
import io.reactivex.functions.Consumer; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import java.util.LinkedList; | |
import java.util.Queue; | |
/** | |
* Special class that can handle {@link Runnable} actions that started after {@link | |
* android.app.Activity#onPause()}, in this case state handler remembers all actions and starts them | |
* after {@link android.app.Activity#onResume()}. All methods shall be called from the UI thread, | |
* otherwise an exception will be thrown. This class is convenient to use while working with | |
* Fragment Transactions to avoid {@link IllegalStateException}. | |
*/ | |
public class StateHandler { | |
private static final Logger LOGGER = LoggerFactory.getLogger(StateHandler.class); | |
private final Queue<Runnable> actions = new LinkedList<>(); | |
private final Handler handler = new Handler(Looper.getMainLooper()); | |
private boolean resumed; | |
/** | |
* Resume the handler. Should be called on the Main/UI thread, otherwise {@link | |
* IllegalThreadStateException} will be thrown. | |
*/ | |
public final void onResume() { | |
ensureOnMainThread(); | |
resumed = true; | |
for (Runnable action = actions.poll(); null != action; action = actions.poll()) { | |
action.run(); | |
} | |
} | |
/** | |
* Pause the handler. Should be called on the Main/UI thread, otherwise {@link | |
* IllegalThreadStateException} will be thrown. | |
*/ | |
public final void onPause() { | |
ensureOnMainThread(); | |
resumed = false; | |
} | |
/** | |
* Store the runnable if we is paused, otherwise handle it now (run immediately). Should be called | |
* on the Main/UI thread, otherwise {@link IllegalThreadStateException} will be thrown. | |
* | |
* @param runnable Runnable to run. | |
*/ | |
public final void run(@NonNull final Runnable runnable) { | |
ensureOnMainThread(); | |
if (resumed) { | |
runnable.run(); | |
} else { | |
actions.offer(runnable); | |
} | |
} | |
/** | |
* Defer the {@link Runnable} action to run after delay, if the current state is paused, then | |
* enqueue the runnable to start after delay when state is resumed. Should be called on the | |
* Main/UI thread, otherwise {@link IllegalThreadStateException} will be thrown. | |
* | |
* @param action Runnable to run. | |
* @param delay the delay before run in milliseconds | |
*/ | |
public void deferPostDelayed(@NonNull final Runnable action, final long delay) { | |
ensureOnMainThread(); | |
run(() -> handler.postDelayed(() -> run(action), delay)); | |
} | |
/** | |
* Enqueue the {@link Runnable} action if current state is paused, otherwise put it on the end of | |
* a message queue and then handle it. Can be called on any thread. | |
* | |
* @param action the {@link Runnable} action. | |
*/ | |
public final void post(@NonNull final Runnable action) { | |
handler.post(() -> run(action)); | |
} | |
/** | |
* Enqueue the {@link Runnable} action if current state is paused, otherwise put it on the end of | |
* message queue with delay and then handle it. Can be called on any thread. | |
* | |
* @param action the {@link Runnable} action. | |
* @param delay the delay before run in milliseconds | |
*/ | |
public void postDelayed(@NonNull final Runnable action, final long delay) { | |
handler.postDelayed(() -> run(action), delay); | |
} | |
/** | |
* Return the {@link Consumer} to subscribe to Rx entity to synchronise emitting an item with the | |
* lifecycle of the Activity or Fragment. Should be called on the Main/UI thread, otherwise {@link | |
* IllegalThreadStateException} will be thrown. | |
* | |
* @param action the subscribing action to run in resumed state | |
* @param <T> The type of an item | |
* @return the {@link Consumer} which should be subscribed to the Rx entity | |
*/ | |
public <T> Consumer<T> subscribeToRun(@NonNull final Consumer<T> action) { | |
ensureOnMainThread(); | |
return item -> run(() -> { | |
try { | |
action.accept(item); | |
} catch (final Exception error) { | |
LOGGER.error("State handler error:", error); | |
} | |
}); | |
} | |
/** | |
* Return the {@link Action} to subscribe to Rx entity to synchronise emitting an item with the | |
* lifecycle of the Activity or Fragment. Should be called on the Main/UI thread, otherwise {@link | |
* IllegalThreadStateException} will be thrown. | |
* | |
* @param action the subscribing action to run in resumed state | |
* @return the {@link Action} which should be subscribed to the Rx entity | |
*/ | |
public Action subscribeToRun(@NonNull final Action action) { | |
ensureOnMainThread(); | |
return () -> run(() -> { | |
try { | |
action.run(); | |
} catch (final Exception error) { | |
LOGGER.error("State handler error:", error); | |
} | |
}); | |
} | |
/** | |
* Remove any pending posts of {@link Runnable} action that are in the message queue. Should be | |
* called on the Main/UI thread, otherwise {@link IllegalThreadStateException} will be thrown. | |
* | |
* @param action the {@link Runnable} to remove | |
*/ | |
public void removeAction(@NonNull final Runnable action) { | |
ensureOnMainThread(); | |
actions.remove(action); | |
handler.removeCallbacks(action); | |
} | |
/** | |
* Clear posts & actions queue. Should be called on the Main/UI thread, otherwise {@link | |
* IllegalThreadStateException} will be thrown. | |
*/ | |
public void clear() { | |
ensureOnMainThread(); | |
actions.clear(); | |
handler.removeCallbacks(null); | |
} | |
private void ensureOnMainThread() { | |
if (Looper.getMainLooper().getThread() != Thread.currentThread()) { | |
throw new IllegalThreadStateException("Called not from the UI thread"); | |
} | |
} | |
/** | |
* The {@link RunnableAction} is useful when it is needed to use uniq actions | |
*/ | |
public abstract static class RunnableAction implements Runnable { | |
private final String id; | |
/** | |
* Create a new action with id. | |
* | |
* @param id The id | |
*/ | |
protected RunnableAction(@NonNull final String id) { | |
this.id = id; | |
} | |
@Override | |
public boolean equals(final Object object) { | |
if (this == object) { | |
return true; | |
} | |
if (object == null || getClass() != object.getClass()) { | |
return false; | |
} | |
final RunnableAction action = (RunnableAction) object; | |
return id.equals(action.id); | |
} | |
@Override | |
public int hashCode() { | |
return id.hashCode(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment