/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.util; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import com.reactivecascade.i.IActionOne; import com.reactivecascade.i.IBindingContext; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; /** * Default binding context implementations * * TODO WIP - this class is not yet complete and in-use */ public class BindingContextUtil { private static final String TAG = BindingContextUtil.class.getSimpleName(); /** * The default implementation of a state-change notification to start and stop data-driven reactive actions * * @param <T> the type of object which will control these state changes */ public static class DefaultBindingContext<T> implements IBindingContext<T> { private final CopyOnWriteArraySet<IActionOne<T>> onOpenActions = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<IActionOne<T>> onCloseActions = new CopyOnWriteArraySet<>(); private final AtomicReference<T> bindingContextAR = new AtomicReference<>(null); /** * * @return */ @Override @CallSuper public boolean isOpen() { return bindingContextAR.get() != null; } /** * * @param t the type of object controlling this binding's lifecycle */ @Override public final void openBindingContext(T t) { if (bindingContextAR.compareAndSet(null, t)) { doBindingContextStateChangeActions(onOpenActions, t); } else { RCLog.d(this, "Can not openBindingContext: illegal transition from " + bindingContextAR.get() + " to " + t); } } /** * * @param t the type of object controlling this binding's lifecyle */ @Override public final void closeBindingContext(T t) { if (bindingContextAR.compareAndSet(t, null)) { doBindingContextStateChangeActions(onCloseActions, t); } else { RCLog.d(this, "Can not closeBindingContext: illegal transition from " + bindingContextAR.get() + " to " + t); } } /** * * @param action */ @Override public final void onOpen(@NonNull IActionOne<T> action) { T t = bindingContextAR.get(); if (t != null) { // Already open- execute now Set<IActionOne<T>> set = new HashSet<>(1); set.add(action); doBindingContextStateChangeActions(set, t); } onOpenActions.add(action); } /** * * @param action performed when binding ends */ @Override public final void onClose(@NonNull IActionOne<T> action) { onCloseActions.add(action); } private void doBindingContextStateChangeActions(Set<IActionOne<T>> actions, T t) { Exception caught = null; for (IActionOne<T> action : actions) { try { action.call(t); } catch (Exception e) { caught = e; RCLog.e(this, "Problem during binding context state change actions- any other actions will still be attempted", e); } } if (caught != null) { throw new IllegalStateException(caught); } } } /** * A parent class for a {@link Fragment} that will provide a binding context to automatically * pause/resume/close reactive bindings */ public static class AsyncFragment extends Fragment { private IBindingContext<Fragment> startStopBindingContext = new DefaultBindingContext<>(); private IBindingContext<Fragment> pauseResumeBindingContext = new DefaultBindingContext<>(); /** * * @return */ @NonNull @UiThread public final IBindingContext getStartStopBindingContext() { return startStopBindingContext; } /** * * @return */ @Nullable @UiThread public final IBindingContext getPauseResumeBindingContext() { return pauseResumeBindingContext; } /** * */ @Override @UiThread @CallSuper public void onResume() { pauseResumeBindingContext.openBindingContext(this); super.onResume(); } /** * */ @Override @UiThread @CallSuper public void onPause() { pauseResumeBindingContext.closeBindingContext(this); pauseResumeBindingContext = new DefaultBindingContext<>(); super.onPause(); } /** * */ @Override @UiThread @CallSuper public void onStop() { startStopBindingContext.closeBindingContext(this); startStopBindingContext = new DefaultBindingContext<>(); super.onStop(); } } /** * A parent class for a {@link FragmentActivity} that will provide a binding context to automatically * pause/resume/close reactive bindings */ public static abstract class AsyncFragmentActivity extends FragmentActivity { /** * Use this binding context if you want your reactive action to end when the fragment closes * * You should create this type of binding in your {@link Fragment#onStart()} method */ private IBindingContext<FragmentActivity> startStopBindingContext = new DefaultBindingContext<>(); private IBindingContext<FragmentActivity> pauseResumeBindingContext = new DefaultBindingContext<>(); /** * * @return a new binding context each time the {@link FragmentActivity} is stopped */ @NonNull @UiThread public final IBindingContext<FragmentActivity> getStartStopBindingContext() { return startStopBindingContext; } /** * Use this binding context if you want your reactive actions to end when the fragment pauses. * * You should create this type of binding in your {@link FragmentActivity#onResume()} method * * @return a new binding context each time the application is paused */ @NonNull @UiThread public final IBindingContext getPauseResumeBindingContext() { return pauseResumeBindingContext; } /** * Run when the binding context goes inactive * * Bound actions are stopped before the activity pauses */ @Override @UiThread @CallSuper public void onPause() { pauseResumeBindingContext.closeBindingContext(this); pauseResumeBindingContext = new DefaultBindingContext<>(); super.onPause(); } /** * Run when the binding context returns from an inactive/paused state * * Bound actions are fired before the activity itself resumes in order to initialize variables * for display */ @Override @UiThread @CallSuper public void onResume() { pauseResumeBindingContext.openBindingContext(this); super.onResume(); } /** * Run when the binding context returns from an inactive/paused state * * Bound actions are fired before the activity itself starts in order to initialize variables * for display */ @Override @UiThread @CallSuper public void onStart() { startStopBindingContext.openBindingContext(this); super.onStart(); } /** * Run when the binding context ends * * Bound actions are stopped before the activity stops */ @Override @UiThread @CallSuper public void onStop() { startStopBindingContext.closeBindingContext(this); startStopBindingContext = new DefaultBindingContext<>(); super.onStop(); } } }