Git Product home page Git Product logo

grox's Introduction

Grox

Grox helps to maintain the state of Java / Android apps.






Understanding Grox

Grox Overview

Video

Grox video

We have a nice video to explain how the Grox sample app works.

Wiki

Visit the Grox wiki

Grox in a nutshell

Grox provides developers with the basic bricks to:

  • create a state of a UI / Application
  • perform pure changes on this state,
  • be notified of state changes (i.e. to update the UI)
  • perform other "side-effects" operations (network calls, manipulating files, etc.)
  • log, persist, create an history of states and state changes

Basic Usage

Very simple example:

//create a store with an initial state
Store<String> store = new Store<>("Hello");
//when the store's state changes, update your UI
states(store).subscribe(System.out::println);
//start dispatching actions to your store...
store.dispatch(oldState -> oldState + " Grox");

A command example

public class RefreshResultCommand implements Command {
 @Override
  public Observable<Action> actions() {
    return getResultFromServer() //via retrofit
        .subscribeOn(io())
        .map(ChangeResultAction::new) //convert all your results into actions
        .cast(Action.class)
        .onErrorReturn(ErrorAction::new) //including errors
        .startWith(fromCallable(RefreshAction::new)); //and progress
  }
}

//then use your command via Rx + RxBinding
subscriptions.add(
        clicks(button)
            .map(click -> new RefreshResultCommand())
            .flatMap(Command::actions)
            .subscribe(store::dispatch));

Note that such a command should be unsubscribed from when the UI element (e.g. an activity) containing the button button will no longer be alive. Otherwise, the Rx chain would leak it.

However, if you preserve your store accross configuration changes (using ViewModels, Dependency Injection (Toothpick/Dagger), retained fragments, etc.), you can also execute commands independently of the lifecycle of the UI:

//then use your command via Rx + RxBinding
subscriptions.add(
        clicks(button)
            .subscribe(click -> new RefreshResultCommand()
                                .actions()
                                .subscribe(store::dispatch)));

In this case, only the outer chain needs to be unsubcribed from when the UI elements are not alive anymore, the inner chain will be preserved during rotation and udpate the store even during a configuration change (e.g. a rotation), and the UI will display the latest when connecting to the store when the rotation is complete. A fine grained management of resources would unsubscribe from the inner chain when the store is not alive anymore.

Browse Grox sample for more details.

Setup

    //note that Grox is also available without Rx dependencies
    implementation 'com.groupon.grox:grox-core-rx:x.y.z'
    //Grox commands artifacts do depend on Rx (1 or 2)
    implementation 'com.groupon.grox:grox-commands-rx:x.y.z'
    implementation 'com.groupon.grox:grox-commands-rx2:x.y.z'

Main features

The main features of Grox are:

  • unify state management. All parts of an app, all screens for instance, can use Grox to unify their handling of state.
  • allows time travel debugging, undo, redo, logging, etc. via middlewares.
  • simple API. Grox is inspired by Redux & Flux but offers a simpler approach.
  • easily integrated with Rx1 and 2. Note that it is also possible to use Grox without Rx.
  • Grox only relies on a few concepts: States, Actions, Stores, MiddleWare & Commands (detailed below).
  • facilitates using immutable states, but without enforcing it for more flexibility. You can use any solution for immutability (Auto-Value, Immutables, Kotlin, etc..) or not use immutability at all if you don't want to.
  • Grox can be used with the Android Arch components, or without them.

Links

Conferences, talks & articles

Credits

The following people have been active contributors to the first version of Grox:

  • Shaheen Ghiassy
  • Michael Ma
  • Matthijs Mullender
  • Turcu Alin
  • Samuel Guirado Navarro
  • Keith Smyth
  • Stephane Nicolas

Inspired by

Grox - Java library for state management, inspired by Flux, Redux, Cycle, and Rx Managed State.

grox's People

Contributors

alin-turcu avatar code-twister avatar gracefulife avatar saguinav avatar stephanenicolas avatar wongcain avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grox's Issues

Grox doesn't notify anyone when an action produces an error when trying to change the state

Grox store don't call any listener / "subscriber of the states(store)" when an error happens during the action's newState method. The errors seem to just be swallowed by Grox.

Here is an example stackTrace:

at com.groupon.newdealdetails.local.grox.RefreshDealAction.newState(RefreshDealAction.java:47)
                                                                      at com.groupon.newdealdetails.local.grox.RefreshDealAction.newState(RefreshDealAction.java:16)
                                                                      at com.groupon.grox.Store$CallReducerMiddleware.intercept(Store.java:186)
                                                                      at com.groupon.grox.RealMiddlewareChain.proceed(RealMiddlewareChain.java:76)
                                                                      at com.groupon.misc.DebugMiddlewareChain.logStateAndAction(DebugMiddlewareChain.java:44)
                                                                      at com.groupon.misc.DebugMiddlewareChain.intercept(DebugMiddlewareChain.java:31)
                                                                      at com.groupon.grox.RealMiddlewareChain.proceed(RealMiddlewareChain.java:76)
                                                                      at com.groupon.grox.Store$NotifySubscribersMiddleware.intercept(Store.java:198)
                                                                      at com.groupon.grox.RealMiddlewareChain.proceed(RealMiddlewareChain.java:76)
                                                                      at com.groupon.grox.Store.emitSequentially(Store.java:86)
                                                                      at com.groupon.grox.Store.dispatch(Store.java:79)
                                                                      at com.groupon.newdealdetails.local.LocalDealDetailsFragment$$Lambda$10.call(Unknown Source:4)
                                                                      at rx.internal.util.ActionSubscriber.onNext(ActionSubscriber.java:39)
                                                                      at rx.observers.SafeSubscriber.onNext(SafeSubscriber.java:134)
                                                                      at rx.internal.operators.OnSubscribeOnAssembly$OnAssemblySubscriber.onNext(OnSubscribeOnAssembly.java:124)
                                                                      at rx.observers.SerializedObserver.onNext(SerializedObserver.java:91)
                                                                      at rx.observers.SerializedSubscriber.onNext(SerializedSubscriber.java:94)
                                                                      at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.innerNext(OnSubscribeConcatMap.java:182)
                                                                      at rx.internal.operators.OnSubscribeConcatMap$ConcatMapInnerSubscriber.onNext(OnSubscribeConcatMap.java:335)
                                                                      at rx.internal.operators.OnSubscribeOnAssembly$OnAssemblySubscriber.onNext(OnSubscribeOnAssembly.java:124)
                                                                      at rx.internal.producers.SingleProducer.request(SingleProducer.java:65)
                                                                      at rx.internal.producers.ProducerArbiter.setProducer(ProducerArbiter.java:126)
                                                                      at rx.internal.operators.OnSubscribeConcatMap$ConcatMapInnerSubscriber.setProducer(OnSubscribeConcatMap.java:329)
                                                                      at rx.Subscriber.setProducer(Subscriber.java:205)
                                                                      at rx.internal.operators.SingleLiftObservableOperator$WrapSubscriberIntoSingle.onSuccess(SingleLiftObservableOperator.java:76)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle$OnAssemblySingleSubscriber.onSuccess(OnSubscribeOnAssemblySingle.java:70)
                                                                      at rx.internal.operators.SingleOnErrorReturn$OnErrorReturnsSingleSubscriber.onSuccess(SingleOnErrorReturn.java:61)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle$OnAssemblySingleSubscriber.onSuccess(OnSubscribeOnAssemblySingle.java:70)
                                                                      at rx.internal.operators.SingleOnSubscribeMap$MapSubscriber.onSuccess(SingleOnSubscribeMap.java:74)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle$OnAssemblySingleSubscriber.onSuccess(OnSubscribeOnAssemblySingle.java:70)
                                                                      at rx.internal.operators.SingleDoOnEvent$SingleDoOnEventSubscriber.onSuccess(SingleDoOnEvent.java:63)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle$OnAssemblySingleSubscriber.onSuccess(OnSubscribeOnAssemblySingle.java:70)
                                                                      at rx.Single$13$1$1.onSuccess(Single.java:2021)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle$OnAssemblySingleSubscriber.onSuccess(OnSubscribeOnAssemblySingle.java:70)
                                                                      at rx.internal.operators.SingleFromCallable.call(SingleFromCallable.java:48)
                                                                      at rx.internal.operators.SingleFromCallable.call(SingleFromCallable.java:29)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle.call(OnSubscribeOnAssemblySingle.java:47)
                                                                      at rx.internal.operators.OnSubscribeOnAssemblySingle.call(OnSubscribeOnAssemblySingle.java:28)
                                                                      at rx.Single.subscribe(Single.java:1967)
                                                                      at rx.Single$13$1.call(Single.java:2039)
                                                                      at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
                                                                      at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
                                                                      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
                                                                        at java.util.concurrent.Futu

core-rx and core-rx2 have class name collision when used in same project

Surprising as it may sound, I have a project with RxJava1/Grox Rx1 in one module, and RxJava2/Grox Rx2 in another. They work fine on their own, but when bringing them together, there's a collision as grox-core-rx and grox-core-rx2 use the same package and class name.

Most libraries solve this evolution problem by namespacing the Rx related classes under rx1 or rx2. Would this be something Grox would be willing to support?

Add full License

The license file we use is the short header for source files.
We should also have a long license which won't be checked by the license plugin.

Generic Commands?

I am curious why Command does not make use of generics like Action does. Wouldn't it be better to define Command like this?

public interface Command<STATE> {
  Observable<? extends Action<STATE> actions();
}

StateChangeListener being called after unsubscribing [concurrency]

onStateChanged method from a StateChangeListener can be called after it has unsubscribe d.

Steps:

  1. We have a Store S and Listener L.
  2. In Thread 1, S starts notifying listeners using NotifySubscribersMiddleware .
  3. CopyOnWriteArrayList is used to keep the listeners. Therefore, we iterate using a copy of the list that won't be changed during the iterator loop.
  4. In Thread 2, L calls unsubscribe to unsubscribe from the Store S. This happens before step 3 is over and L is notified.
  5. S notifies L calling onStateChanged, even thought it is already unsubscribed.

The scenario looks difficult to reproduce, however the consequences can be fatal.

Change the sample to use concat instead of startWith

There is a subtle problem in your use of startWith() in LoginCommand.java: you use startWith() on the io() scheduler: that means, that the LogInProgressAction will not be emitted right away: so when you use this initial state to disable the login button, there is a possibility that the user presses the button twice.

So we should use concat:

Observable
  .just(LogInProgressAction)
  .concatWith( loginApiClient
    …
  )

State modification is visible too early to subscribers

if there are 2 subscribers (S0 & S1), and S0 modifies the state of the store in its RxChain, then S1 will see the state after modification by S1.

It shouldn't be llike this.
--> S0 & S1 should both see the original state and they both see the modified state after.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.