• Stars
    star
    160
  • Rank 234,703 (Top 5 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created about 9 years ago
  • Updated almost 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

"Predictable state container" implementation, inspired by Redux.

Droidux

Build Status Download Apache 2.0 Android Arsenal

Droidux is "predictable state container" implementation, inspired by Redux.

Features

Droidux is influenced by Three principles of Redux.

  • Single source of truth
    • The state of your whole application is stored in an object tree inside a single store.
  • State is read-only
    • The only way to mutate the state is to emit an action, an object describing what happened.
  • Mutations are written as pure functions
    • To specify how the state tree is transformed by actions, you write pure reducers.

Three Principles | Redux

Features of Droidux are following:

  • All mutations can be observed via Flowable from RxJava
  • All mutations are automatically notified to views via Data Binding

Data flow

Droidux data flow

see also: Introduction to Redux // Speaker Deck (in Japanese)

Installation

Droidux depends on RxJava and Data Binding. Add to your project build.gradle file:

apply plugin: 'com.android.application'

dependencies {
  compile 'info.izumin.android:droidux:0.6.0'
  compile 'io.reactivex.rxjava2:rxjava:2.1.8'
  compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
  annotationProcessor 'info.izumin.android:droidux-processor:0.6.0'
}

And also you need to setup Data Binding.

When you use AsyncAction, you need to add droidux-thunk.

compile 'info.izumin.android:droidux-thunk:0.3.0'

Usage

Quick example

/**
 * This is a state class.
 * It can be as simple as possible implementation, like POJO, or immutable object. 
 */
public class Counter {
    private final int count;

    public Counter(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }
}

/**
 * This is a reducer class.
 * It should be applied @Reducer annotation is given a state class as an argument.
 * It describe whether the reducer should handle which actions.
 */
@Reducer(Counter.class)
public class CounterReducer {

    /**
     * This is a method to handle actions.
     * It should be applied @Dispatchable annotation is given an action class as an parameter.
     * It describe how to transform the state into the next state when dispatched actions.
     * It should return the next state instance, and it is preferred instantiate the new state.
     *
     * This example handle IncrementCountAction,
     + and it returns new counter instance that state is incremented.
     */
    @Dispatchable(IncrementCountAction.class)
    public Counter increment(Counter state) {
        return new Counter(state.getCount() + 1);
    }

    @Dispatchable(DecrementCountAction.class)
    public Counter decrement(Counter state) {
        return new Counter(state.getCount() - 1);
    }

    @Dispatchable(ClearCountAction.class)
    public Counter clear() {
        return new Counter(0);
    }
}


/**
 * This is a store interface.
 * It should be applied @Store annotation and passing reducer classes as parameters.
 * Droidux generates an implementation of getter method, observe method and dispatch method from user-defined interface.
 */
@Store(CounterReducer.class)
public interface CounterStore extends BaseStore {
    Counter getCounter();
    Flowable<Counter> observeCounter();
}

/**
 * They are action classes. They should extend Action class.
 */
public class IncrementCountAction implements Action {}
public class DecrementCountAction implements Action {}
public class ClearCountAction implements Action {}


// Instantiate a Droidux store holding the state of your app.
// Its class is generated automatically from Reducer class.
// 
// The instantiating should use Builder class,
// and it should register a reducer instance and an initial state.
// 
// Its APIs in this example are following:
// - Flowable<Action> dispatch(Action action)
// - Flowable<Counter> observeCounter()
// - Counter getCounter()
CounterStore store = DroiduxCounterStore.builder()
        .setReducer(new CounterReducer(), new Counter(0))
        .build();                                       // Counter: 0

// You can observe to the updates using RxJava interface. 
store.observe((counter) -> Log.d(TAG, counter.toString()));

// The only way to mutate the internal state is to dispatch an action.
store.dispatch(new IncrementCountAction()).subscribe(); // Counter: 1
store.dispatch(new IncrementCountAction()).subscribe(); // Counter: 2
store.dispatch(new IncrementCountAction()).subscribe(); // Counter: 3

store.dispatch(new DecrementCountAction()).subscribe(); // Counter: 2

store.dispatch(new ClearCountAction()).subscribe();     // Counter: 0

Data Binding

// If you use databinding, yor store interface must extend `android.databinding.Observable`.
@Store(CounterReducer.class)
public interface CounterStore extends BaseStore, android.databinding.Observable {
    // You should annotate the getter method with @Bindable
    @Bindable Counter getCounter();
}

CounterStore store = DroiduxCounterStore.builder()
        // Pass the field id generated by DataBinding annotation processor.
        .setReducer(new CounterReducer(), new Counter(0), BR.counter)
        .build();

Layout file is following:

<layout>
    <data>
        <variable android:name="store" android:type="CounterStore" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@{store.counter}" />
        
    </RelativeLayout>
</layout>

Combined store

@Store({CounterReducer.class, TodoListReducer.class})
class RootStore extends BaseStore {
    Counter getCounter();
    Flowable<Counter> observeCounter();
    TodoList getTodoList();
    Flowable<TodoList> observeTodoList();
}


RootStore store = DroiduxRootStore.builder()
        .setReducer(new CounterReducer(), new Counter(0))
        .setReducer(new TodoListReducer(), new TodoList())
        .addMiddleware(new Logger())
        .build();

store.dispatch(new IncrementCountAction()).subscribe();     // Counter: 1, Todo: 0
store.dispatch(new AddTodoAction("new task")).subscribe();  // Counter: 1, Todo: 1

Middleware

class Logger extends Middleware<CounterStore> {
    @Override
    public Flowable<Action> beforeDispatch(Action action) {
        Log.d("[prev counter]", String.valueOf(getStore().count()));
        Log.d("[action]", action.getClass().getSimpleName());
        return Flowable.just(action);
    }

    @Override
    public Flowable<Action> afterDispatch(Action action) {
        Log.d("[next counter]", String.valueOf(getStore().count()));
        return Flowable.just(action);
    }
}

// Instantiate store class 
CounterStore store = DroiduxCounterStore.builder()
        .setReducer(new CounterReducer(), new Counter(0))
        .addMiddleware(new Logger())        // apply logger middleware
        .build();                           // Counter: 0

store.dispatch(new IncrementCountAction()).subscribe();
// logcat:
// [prev counter]: 0
// [action]: IncrementCountAction
// [next counter]: 1

store.dispatch(new IncrementCountAction()).subscribe();
// logcat:
// [prev counter]: 1
// [action]: IncrementCountAction
// [next counter]: 2

store.dispatch(new ClearCountAction()).subscribe();
// logcat:
// [prev counter]: 2
// [action]: ClearCountAction
// [next counter]: 0

Undo / Redo

class TodoList extends ArrayList<TodoList.Todo> implements UndoableState<TodoList> {
    @Override
    public TodoList clone() {
        // ...
    }

    public static Todo {
        // ...
    }
}

@Undoable
@Reducer(TodoList.class)
class TodoListReducer {
    @Dispatchable(AddTodoAction.class)
    public TodoList add(TodoList state, AddTodoAction action) {
        // ...
    }

    @Dispatchable(CompleteTodoAction.class)
    public TodoList complete(TodoList state, CompleteTodoAction action) {
        // ...
    }
}

@Store(TodoListReducer.class)
public interface TodoListStore {
    TodoList todoList();
    Flowable<TodoList> observeTodoList();
}

class AddTodoAction implements Action {
    // ...
}

class CompleteTodoAction implements Action {
    // ...
}


TodoListStore store = DroiduxTodoListStore.builder()
        .setReducer(new TodoListReducer(), new TodoList())
        .build();

store.dispatch(new AddTodoAction("item 1")).subscribe();        // ["item 1"]
store.dispatch(new AddTodoAction("item 2")).subscribe();        // ["item 1", "item 2"]
store.dispatch(new AddTodoAction("item 3")).subscribe();        // ["item 1", "item 2", "item 3"]
store.dispatch(new CompleteTodoAction("item 2")).subscribe();   // ["item 1", "item 3"]
store.dispatch(new AddTodoAction("item 4")).subscribe();        // ["item 1", "item 3", "item 4"]

store.dispatch(new UndoAction(TodoList.class)).subscribe();
// => ["item 1", "item 3"]

store.dispatch(new UndoAction(TodoList.class)).subscribe();
// => ["item 1", "item 2", "item 3"]

store.dispatch(new RedoAction(TodoList.class)).subscribe();
// => ["item 1", "item 3"]

Async action

Use droidux-thunk.

class FetchTodoListAction implements AsyncAction {
    private final TodoListApi client;

    public FetchTodoListAction(TodoListApi client) {
        this.client = client;
    }

    public Flowable<ReceiveTodoListAction> call(Dispatcher dispatcher) {
        return dispatcher.dispatch(new DoingFetchAction())
                .flatMap(_action -> client.fetch())
                .map(todoList -> {
                    this.todoList = todoList;
                    return new ReceiveTodoListAction(todoList);
                });
    }
}

class ReceiveTodoListAction implements Action {
    private final TodoList todoList;

    public ReceiveTodoListAction(TodoList todoList) {
        this.todoList = todoList;
    }

    public TodoList getTodoList() {
        return todoList;
    }
}


TodoListStore store = DroiduxTodoListStore.builder()
        .setReducer(new TodoListReducer(), new TodoList())
        .addMiddleware(new ThunkMiddleware())
        .build();


store.dispatch(new FetchTodoListAction(client)).subscribe();

Examples

License

Copyright 2015 izumin5210

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.

More Repositories

1

grapi

😮 A surprisingly easy API server and generator in gRPC and Go
Go
427
star
2

gex

The implementation of "clarify best practice for tool dependencies".
Go
51
star
3

Bletia

Promisified BLE library for Android.
Java
30
star
4

CiPointCloudViewer

Capturing and visualizing point clouds retrieved from multiple RGB-D cameras
C++
28
star
5

rspec-validator_spec_helper

Provide dummy class for validator spec
Ruby
20
star
6

cgt

🎨 Painting go test outputs
Go
15
star
7

hx

🌏 Developer-friendly, Real-World-ready and extensible HTTP client for Go
Go
12
star
8

action-homebrew-tap

JavaScript
12
star
9

dotfiles

My kitchen environment for cooking awesome products🍣🍣
Lua
10
star
10

json-schema-parser

Parse JSON Schema and resolve `$ref` fields.
TypeScript
10
star
11

json-schema-mockifier

Generate mock object from JSON Schema (HyperSchema v4).
JavaScript
10
star
12

clig

boilerplate generator and utilities for CLI tools in Go
Go
10
star
13

protogql

Build GraphQL schema and server from Protobuf
Go
9
star
14

nrgrpc

📈 gRPC `stats.Handler` implementation to measure and send performances metrics to New Relic
Go
9
star
15

rspec-cheki

Support snapshot testing, inspired Jest.
Ruby
8
star
16

action-go-crossbuild

Build Go applications for multiplatform on GitHub Actions
JavaScript
6
star
17

scaffold

Customizable templates generator, inspired by Rails
Go
6
star
18

tomatone

Simple pomodoro timer in your menubar
JavaScript
6
star
19

Sunazuri

esa.io client application for Android (unofficial)
Java
6
star
20

emojipack-for-devicon

Shell
6
star
21

pubee

Pluggable Pub/Sub Publisher
Go
5
star
22

ro

Redis Objects to Go
Go
5
star
23

redisync

Synchronization primitives with Redis
Go
4
star
24

jsonschema-study

Ruby
4
star
25

notebook-dockerfiles

Shell
4
star
26

actions-reviewdog

Dockerfile
4
star
27

actopus

Ruby
4
star
28

RxBleScanner

Java
4
star
29

homebrew-tools

Ruby
4
star
30

OHP

OHP is Hacker's Presentation writer with Markdown and CSS
JavaScript
3
star
31

gentleman-logger

Simplest logging plugin for gentleman HTTP client
Go
3
star
32

android-dockerfile

Ruby
3
star
33

orbs-for-tools

♻️ CircleCI Orbs for Continous-Delivery for Tools
Makefile
2
star
34

ridgepole-docker

Run ridgepole in docker
Dockerfile
2
star
35

hitorigoto_reporter

Post daily reports of slack channel to esa.io
Ruby
2
star
36

DialogFragmentBuilder_old

it makes you can use DialogFragment easily.
Java
2
star
37

ghsync

Go
2
star
38

nrredigo

Redigo connection wrapper with New Relic instrumentation for Go.
Go
2
star
39

go-dagger

Go
2
star
40

actions-github-release

Shell
1
star
41

televideo

Proxy server to record HTTP request/response with VCR
Ruby
1
star
42

action-setup-protobuf

JavaScript
1
star
43

font-awesome-helper

Useful helper for Font Awesome
Ruby
1
star
44

apidocgen

Dockerfile
1
star
45

dform

CLI to manage Dgraph schema
Go
1
star
46

ViewBorderHelper

Provides CSS-like border style to your custom views.
Java
1
star
47

nrgorm

Go
1
star
48

wannabe

Make everything be boolean (inspired by wannabe_bool)
Go
1
star
49

gin-zap

Go
1
star
50

httplogger

Go
1
star
51

SakeRadar

Java
1
star
52

go-cliapp-template

Template of a basic cli application with Golang
Go
1
star
53

actions-go-crossbuild-with-container

Dockerfile
1
star
54

graphql-fragment-mask

Mask GraphQL query result with Fragment
TypeScript
1
star
55

obsidian-iceberg

CSS
1
star
56

nrsql

SQL database driver wrapper with New Relic instrumentation for Go.
Go
1
star
57

DaggerHelpers

Java
1
star
58

scopedog

Democratize ActiveRecord's scopes
Ruby
1
star
59

asset_paths_from_manifest

Provides view helpers to compute full paths for assets from manifest json file
Ruby
1
star