Skip to content

Instantly share code, notes, and snippets.

@ultraon
Last active May 24, 2017 11:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ultraon/895b5c2403f28c43ffbed7fd729a3812 to your computer and use it in GitHub Desktop.
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…
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