Skip to content

Using FluxC

Alex edited this page Jun 7, 2017 · 20 revisions

There are a few working examples of using FluxC as a library. See the FluxC Example app and Instaflux.

The following examples use snippets from the FluxC Example app.

Initial setup

See the example app for a working example of FluxC in a new app. Here are step by step instructions:

1. Set up FluxC imports

FluxC uses JitPack to provide ready-to-use artifacts of any branch or commit.

  1. Add the JitPack repository to your project's root build.gradle:
allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}
  1. Add the FluxC dependency to your module-level build.gradle:
dependencies {
    compile 'com.github.wordpress-mobile.WordPress-FluxC-Android:fluxc:develop-SNAPSHOT'
}

Note: Using develop-SNAPSHOT will always use the latest state (snapshot) of FluxC's develop branch, which may break your build as changes are made to FluxC. You can instead point to any commit hash permanently using:

dependencies {
    compile 'com.github.wordpress-mobile.WordPress-FluxC-Android:fluxc:2fbd31a89315f826a6028af4285114bf4e09b126'
}

You can also point to the latest snapshot of any branch (replace / with ~ in the branch name):

dependencies {
    compile 'com.github.wordpress-mobile.WordPress-FluxC-Android:fluxc:issue~555-my-working-branch'
}

You'll also need to add Dagger to your module-level build.gradle:

compile 'com.google.dagger:dagger:2.0.2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
provided 'org.glassfish:javax.annotation:10.0-b28'

2. Set up your AppComponent

Next, create an AppComponent interface, which manages the dependency injection needed to use FluxC in your app (working example):

package com.example.app;

@Singleton
@Component(modules = {
        AppContextModule.class,
        AppSecretsModule.class,
        ReleaseOkHttpClientModule.class,
        ReleaseBaseModule.class,
        ReleaseNetworkModule.class,
        ReleaseStoreModule.class
})
public interface AppComponent {
    void inject(ExampleApp object);
}

You'll also need an Application class (called ExampleApp in the demo code above) (working example):

package com.example.app;

public class ExampleApp extends Application {
    protected AppComponent mComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        initDaggerComponent();
        component().inject(this);
        WellSql.init(new WellSqlConfig(getApplicationContext()));
    }

    public AppComponent component() {
        return mComponent;
    }

    protected void initDaggerComponent() {
        mComponent = DaggerAppComponent.builder()
                .appContextModule(new AppContextModule(getApplicationContext()))
                .build();
    }
}

Don't forget to set the app name in AndroidManifest.xml:

<manifest ...>
    <application
        android:name=".ExampleApp"
    ...
    </application>
</manifest>

You'll also need an AppSecretsModule.

At this point, you can build the app. If you want to be able to authenticate with WordPress.com accounts, however, you'll also need to set up the OAuth ID and SECRET for AppSecretsModule to use.

3. Set up OAuth for WordPress.com support

Set up a gradle.properties file in your module directory with the fields:

wp.OAUTH.APP.ID = FIXME
wp.OAUTH.APP.SECRET = FIXME

And add this to your gradle.properties (working example):

// Add properties named "wp.xxx" to our BuildConfig
android.buildTypes.all { buildType ->
    project.properties.any { property ->
        if (property.key.toLowerCase().startsWith("wp.")) {
            buildType.buildConfigField "String", property.key.replace("wp.", "").replace(".", "_").toUpperCase(),
                    "\"${property.value}\""
        }
    }
}

(You'll want to add gradle.properties to your .gitignore so you don't commit your app secret.

The basic setup for FluxC is now done! The next step is to 'connect' an Activity or Fragment to FluxC so you can get started sending actions and using Stores.

Using a Store in an Activity or Fragment

  • Add the activity to the Component
  • Inject the fragment into the current Component instance when the activity is created onCreate:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ((ExampleApp) getApplication()).component().inject(this);
    ...
  • Inject any needed FluxC stores:
public class PostsFragment extends Fragment {
    @Inject PostStore mPostStore;
    ...

The injected PostStore can now be used to fetch data from the store.

mPostStore.getPostsForSite(mSite);

Only read actions are available in this way - anything that involves making a network request or altering the local storage requires use of Actions and the Dispatcher.

Using the Dispatcher

The same prerequisites as above apply for the Dispatcher - the activity using it must be registered with the Component, and the activity should be injected.

Next, inject the Dispatcher into the activity:

public class PostsFragment extends Fragment {
    @Inject PostStore mPostStore;
    @Inject Dispatcher mDispatcher;
    ...

An additional requirement for using the Dispatcher is that the actvity needs to register and unregister itself with the dispatcher as part of its life cycle. For example:

@Override
public void onStart() {
    super.onStart();
    mDispatcher.register(this);
}

@Override
public void onStop() {
    super.onStop();
    mDispatcher.unregister(this);
}

We can now dispatch actions. For example, let's fetch a list of posts from a user's site. This will involve the PostStore, and the FETCH_POSTS action (all actions supported by each store can be found here).

To dispatch an action, we must first create an Action object. All available actions for a store will be accessible as methods of that store's ActionBuilder. For example, to make a FETCH_POSTS request, we build a FETCH_POSTS action:

PostActionBuilder.newFetchPostAction();

Each method requires a specific Payload type (some may require no Payload at all). In this case, we need to pass newFetchPostAction() a FetchPostsPayload.

FetchPostsPayload payload = new FetchPostsPayload(mSite);
mDispatcher.dispatch(PostActionBuilder.newFetchPostsAction(payload));

Dispatched actions will eventually result (asynchronously) in an OnChanged event, which the dispatching activity or fragment should listen for if it needs to act on the results of the action.

Note: For the dispatcher to work, the activity or fragment using it must be subscribed to at least one OnChanged event.

Receiving OnChanged events

Any dispatched action will eventually result in a OnChanged event. This event will be emitted asynchronously, and the calling activity needs to listen for the event in order to be notified of the result of the action.

For example, a FETCH_POSTS action will result in a OnPostChanged event once the network call has completed and the database updated:

@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPostChanged(OnPostChanged event) {
    if (event.isError()) {
        prependToLog("Error from " + event.causeOfChange + " - error: " + event.error.type);
        return;
    }

    if (event.causeOfChange.equals(PostAction.FETCH_POSTS)) {
        prependToLog("Fetched " + event.rowsAffected + " posts from: " + firstSite.getName());
    }
}

Notes:

  • All actions generate an OnChanged event, whether they succeeded or not
  • The same OnChanged event is emitted in case of success or failure; the difference is that OnChanged.isError() will be true in case of failure, and OnChanged.error will contain error details
  • Some OnChanged events have a causeOfChange field, which specifies the action that resulted in this OnChanged event. For example, the FETCH_POSTS and DELETE_POST actions both result in an OnPostChanged action, and we can check the origin using causeOfChange. OnPostUploaded, on the other hand, is only emitted as a result of PUSH_POST, and so doesn't have a causeOfChange.

Working with Models

Creating models

The way to create a new model depends on what you intend to do with it.

'Draftable' models

'Draftable' models, like posts and media, which we store local-only versions of, should not be created directly using new XModel(). This is because the model won't exist in the local DB and so lack a local ID, and this may cause unexpected behaviour when attempting to save the model later on.

To receive a new model with a local ID, use instantiate methods from the corresponding Store. For example,

mPostStore.instantiatePostModel();

Non-'draftable' models

Non-'draftable' models, that aren't intended to be persisted before they're uploaded, should be instantiated as normal objects (new XModel()).

Using models

  • For consistency and clarity, avoid passing pieces of models (e.g. the ID) between activities/fragments/methods/services, and instead serialize and de-serialize the entire model.
  • If you are wary that the model might have changed when you receive it (in your method/activity/fragment/service), retrieve a fresh copy from the corresponding store

Note: An exception to this is the PostModel, which is known to be able to hold up to 2MB in content. For these cases, serializing/deserializing a big object to pass it through to activities/fragments in Android could lead to potential problems on low memory devices. In cases like this, it's safest to pass the local ID of the post through the intent and get a fresh copy of the post from the store in the new activity or fragment.