com.groupon.grox.Store Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grox-core Show documentation
Show all versions of grox-core Show documentation
Maintains the state of Android/Java apps.
/*
* Copyright (c) 2017, Groupon, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.groupon.grox;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Like Redux Stores, stores in grox are:
*
*
* - responsible for holding the state.
*
- state is immutable.
*
- stores accept {@link StateChangeListener} that will be notified of state changes.
* Unlike in redux, the listeners are not unsubscribed automatically.
*
- stores also use {@link Middleware} like in redux.
*
- they are fully testable.
*
*
* @param the class of the state.
*/
public class Store {
/** The current state of the store. */
private STATE state;
/** The list of internal middle wares. */
private final List> middlewares = new ArrayList<>();
/** The list of all state change listeners that will get notified of state changes. */
private List> stateChangeListeners = new CopyOnWriteArrayList<>();
public Store(STATE initialState, Middleware... middlewares) {
this.state = initialState;
this.middlewares.add(new NotifySubscribersMiddleware());
this.middlewares.addAll(asList(middlewares));
this.middlewares.add(new CallReducerMiddleware());
}
/**
* Dispatches an action in the store. The action will go through the chain of the middle wares and
* then the action will get executed, and create a new state that will replace the current state
* in the store. The state change will be notified to listeners.
*
* @param action the action to be executed.
* @see Middleware
* @see StateChangeListener
*/
public synchronized void dispatch(Action action) {
new RealMiddlewareChain<>(this, action, this.middlewares, 0).proceed(action);
}
/** @return the current state of the store. */
public STATE getState() {
return state;
}
/**
* Adds a new {@link StateChangeListener} to the list of listeners that will get notified of state
* changes.
*
* @param listener the listener to be added.
*/
public void subscribe(StateChangeListener listener) {
this.stateChangeListeners.add(listener);
listener.onStateChanged(getState());
}
/**
* Removes a previously added {@link StateChangeListener} from the list of listeners.
*
* @param listener the listener to be removed.
*/
public void unsubscribe(StateChangeListener listener) {
this.stateChangeListeners.remove(listener);
}
/**
* Basically, a middle ware can intercept all actions being dispatched through a store. Unlike in
* Redux, we recommend not to use middle wares to execute asynchronous tasks, like API calls.
* Middle wares in Grox are more about adding general behavior to a store. e.g.: crash reporting,
* logging, undo, etc.
*
* @param the class of the state.
*/
public interface Middleware {
/**
* Allows a middle ware to intercept actions as they go through the store. A middle ware
* must call {@link Chain#proceed} on the chain received as a parameter. This method
* must be called once and only once in each intercept method. The call to {@link
* Chain#proceed} will trigger the remaining middle wares in the chain to have their intercept
* method called. The last middle ware in the store is an internal middle ware that will
* call {@link Action#newState(Object)}. Hence, a middle ware can do things before the
* rest of the middle wares are executed (and the action is executed) and after the rest of the
* middle wares are executed (and the action is executed). Middle wares are added to a
* store at construction time, see {@link #Store(Object, Middleware[])}.
*
* @param chain the chain of all middle wares for the store associated to this middleware.
*/
void intercept(Chain chain);
/**
* Represents the linked list of all middle wares int the store. The order of the middle ware
* corresponds to the order in which the middle wares are passed to the store at construction
* time. See {@link #Store(Object, Middleware[])}.
*
* @param the class of the state.
*/
interface Chain {
/** @return the action being dispatched. */
Action action();
/**
* @return the current state of the store. The state before the call to {@link
* #proceed(Action)} is the state prior to the action being executed. The state after the
* call is the state after the action being executed.
*/
STATE state();
/**
* Triggers the rest of the middle wares to be executed, and ultimately the action to be
* executed.
*
* @param action the action being dispatched in the store.
*/
void proceed(Action action);
}
}
/**
* A listener that will be notified of all state changes taking place in a store.
*
* @param the class of the state.
* @see #subscribe(StateChangeListener)
* @see #unsubscribe(StateChangeListener)
*/
public interface StateChangeListener {
void onStateChanged(STATE newState);
}
/** Internal middle ware that actually executes the action of a middle ware chain. */
private class CallReducerMiddleware implements Middleware {
@Override
public void intercept(Chain chain) {
state = chain.action().newState(state);
}
}
/**
* Internal middle ware that notifies the {@link StateChangeListener} that the store's state has
* changed.
*/
private class NotifySubscribersMiddleware implements Middleware {
@Override
public void intercept(Chain chain) {
chain.proceed(chain.action());
for (Iterator> iterator = stateChangeListeners.iterator();
iterator.hasNext();
) {
StateChangeListener stateChangeListener = iterator.next();
stateChangeListener.onStateChanged(state);
}
}
}
}