12/08/2018, 13:02

Flux Architecture on Android

Giới thiệu về kiến trúc Flux(Flux Architecture) Flux Architecture đã được xây dựng và sử dụng bới Facebook. Mục đích ban đầu của họ khi xây dựng Flux Architecture là cho các dứng dụng web client-side và tất nhiên nó không có ý định xây dựng cho các mobile app.Nhưng với những tính năng và sự đơn ...

Giới thiệu về kiến trúc Flux(Flux Architecture)

Flux Architecture đã được xây dựng và sử dụng bới Facebook. Mục đích ban đầu của họ khi xây dựng Flux Architecture là cho các dứng dụng web client-side và tất nhiên nó không có ý định xây dựng cho các mobile app.Nhưng với những tính năng và sự đơn giản của nó đã được chuyển đổi rất tốt vào trong nhưng Android Project. Bạn có thể xem chi tiết về Flux tại buổi giới thiệu của Facebook tại đay.

flux-graph-simple.png

Có 2 tính năng chính để bạn có thể hiều về Flux:

  • Luồng dữ liệu luôn luôn là 1 chiều Luồng dữ liệu một chiều là tính năng chính trong Flux Architecture làm cho nó trở lên dễ dàng để học.

  • Ứng dụng là được chia vào 3 phần Chính:

    -View: Giao diện ứng dụng. Nó tạo ra các Action để phản hồi với tương tác của người dùng.

    -Dispatcher: Là một Hub trung tâm, chịu trách nhiệm gửi toàn bộ các Action tới mỗi Store.

    **-Store **: Duy trì trạnng thái cho ứng dựng, phản hồi lại các Action với các state cụ thể, thực thi logic, phát ra Change Event khi việc xử lý đã xong.Những sự kiện này đã được sử dụng bởi View cho việc update giao diện. Cả 3 phần giao tiếp với nhau bằng Actions. Action là đối tượng đơn giản được xác định bởi một Type của Action, và chứa data liên quan tới Action đó.

Flux Android Architecture

flux-graph-complete.png

Usage

dependencies {
  compile 'com.hardsoftstudio:rxflux:0.1.2'
}

RxFlux có chứa dependency RxAndroid và Android support v4 để nhận được những tiện lợi của những class like ArrayMap, Pair, etc... Class chính là RxFlux chịu trách nhiệm cho việc xử lý vòng đời của các Activity để notify và register tới các Store và các View. Việc đầu tiên bạn cần phải làm là bắt đầu RxFlux trong ứng dụng.

public class GitHubApp extends Application {
    private RxFlux mRxFlux;

    private GitHubActionCreator mActionCreator;
    public static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mRxFlux = RxFlux.init(this);
        mActionCreator = new GitHubActionCreator(mRxFlux.getDispatcher(), mRxFlux.getSubscriptionManager());
        mContext = this.getApplicationContext();
    }

    public RxFlux getRxFlux() {
        return this.mRxFlux;
    }

    public void setRxFlux(RxFlux rxFlux) {
        this.mRxFlux = rxFlux;
    }

    public GitHubActionCreator getActionCreator() {
        return mActionCreator;
    }

    public void setActionCreator(GitHubActionCreator actionCreator) {
        this.mActionCreator = actionCreator;
    }

    public static GitHubApp get(Context context) {
        return (GitHubApp) context.getApplicationContext();
    }

    public static Context getContext(){
        return mContext;
    }
}

View

Mỗi Activity của ứng dụng phải implement ** RxViewDispatch **. Interface này định nghĩa những phương thức cần cho mỗi view. RxFlux sẽ notify mỗi Activity implement RxViewDispatch trong khi tạo nó và gọi onRxStoresRegister() . Phương thức này gọi các Store cần và register nó.

@Override
    public void onRxStoresRegister() {
        mRepositoriesStore = RepositoriesStore.getInstance(GitHubApp.get(this).getRxFlux().getDispatcher());
        mRepositoriesStore.register();
        mUsersStore = UsersStore.getInstance(GitHubApp.get(this).getRxFlux().getDispatcher());
        mUsersStore.register();
        mApp = GitHubApp.get(MainActivity.this);

    }

*onRxStoreChanged(RxStoreChange change) * sẽ nhận được notify bởi bất ký store nào thay đổi trong app. Do đó bạn phải filter bởi storeId để xác định xem store nào thay đổi.

 @Override
    public void onRxStoreChanged(RxStoreChange change) {
        setLoadingFrame(false);
        switch (change.getStoreId()) {
            case RepositoriesStore.REPOSITORIES_ID:
                switch (change.getRxAction().getType()) {
                    case Actions.GET_REPOSITORIES:
                        mRepoAdapter.setRepos(mRepositoriesStore.getRepositories());
                        break;
                }
                break;
            case UsersStore.USER_STORE_ID:
                switch (change.getRxAction().getType()) {
                    case Actions.GET_USER:
                        UserDetailDialog dialog = UserDetailDialog.newInstance((String) change.getRxAction().getData().get(Keys.ID));
                        dialog.show(getSupportFragmentManager(), UserDetailDialog.class.getName());
                }
        }

    }

RxActionCreator

Bước tiếp theo chúng ta tạo ActionsCreator, để làm vậy chúng ta cần tạo một class mới với việc extends từ * RxActionCreator *. Abastract class này xẽ cung cấp cho chúng ta một số function mở rộng để tạo các action và request. Tôi xin đề nghị là các bạn lên tạo một interface để địn nghĩa các actions và các phương thức như bên dưới:

public interface Actions {
    String GET_REPOSITORIES = "get_repositories";
    String GET_USER = "get_user";

    void getRepositories();

    void getUserDetail(String userId);
}

Và ActionCreator sẽ như bên dưới:

public class GitHubActionCreator extends RxActionCreator implements Actions {

    public GitHubActionCreator(Dispatcher dispatcher, SubscriptionManager manager) {
        super(dispatcher, manager);
    }

    @Override
    public void getRepositories() {
        final RxAction takeRepositoriesAction = newRxAction(GET_REPOSITORIES);
        if (hasRxAction(takeRepositoriesAction)) {
            return;
        }

        addRxAction(takeRepositoriesAction, GitHubAppService.getRxSampleAppApi()
                .getRepositories()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(repos -> postRxAction(newRxAction(GET_REPOSITORIES, Keys.REPOSITORY, repos)),
                        throwable -> postError(takeRepositoriesAction, throwable)));
    }

    @Override
    public void getUserDetail(String userId) {
        final RxAction takeUserAction = newRxAction(GET_USER, Keys.ID, userId);
        if (hasRxAction(takeUserAction)){
            return;
        }

        addRxAction(takeUserAction, GitHubAppService.getRxSampleAppApi().getUser(userId).
                subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(user ->{takeUserAction.getData()
                        .put(Keys.User, user);postRxAction(takeUserAction);},
                        throwable -> {postError(takeUserAction,throwable);}));
    }
}

RxStore

Đây là class đại diện cho Model + Logic của App. Để đơn giản cho việc tạo nó, mỗi Store bạn nên extends từ một abstract class là RxStore.

public class RepositoriesStore extends RxStore implements RepositoriesStoreInterface {
    public static final String REPOSITORIES_ID = "Repositories";
    private static RepositoriesStore mInstance;
    private ArrayList<GitHubRepo> mGitHubRepos;

    public RepositoriesStore(Dispatcher dispatcher) {
        super(dispatcher);
    }

    public static synchronized RepositoriesStore getInstance(Dispatcher dispatcher) {
        if (mInstance == null) {
            mInstance = new RepositoriesStore(dispatcher);
        }
        return mInstance;
    }

    @Override
    public ArrayList<GitHubRepo> getRepositories() {
        return mGitHubRepos == null ? new ArrayList<>() : mGitHubRepos;
    }

    @Override
    public void onRxAction(RxAction action) {
        switch (action.getType()) {
            case Actions.GET_REPOSITORIES:
                this.mGitHubRepos = (ArrayList<GitHubRepo>) action.getData().get(Keys.REPOSITORY);
                break;
            default:
                break;
        }
        postChange(new RxStoreChange(REPOSITORIES_ID, action));
    }
}

*RepositoriesStoreInterface.java *

public interface RepositoriesStoreInterface {
    ArrayList<GitHubRepo> getRepositories();
}

Trong Project Sample này tôi sẽ lấy về các thông tin của các Repository public trên github theo API: *https://api.github.com/repositories * Sau đây là một vài class bạn cần phải thực hiện: Models:

GitHubRepo.java

public class GitHubRepo {
    private int id;
    private String name;
    @SerializedName("full_name")
    private String fullName;
    private GitUser owner;
    private String description;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public GitUser getOwner() {
        return owner;
    }

    public void setOwner(GitUser owner) {
        this.owner = owner;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

GitUser.java

public class GitUser {
    @SerializedName("login")
    @Expose
    public String login;
    @SerializedName("id")
    @Expose
    public int id;
    @SerializedName("avatar_url")
    @Expose
    public String avatarUrl;
    @SerializedName("gravatar_id")
    @Expose
    public String gravatarId;
    @SerializedName("url")
    @Expose
    public String url;
    @SerializedName("html_url")
    @Expose
    public String htmlUrl;
    @SerializedName("followers_url")
    @Expose
    public String followersUrl;
    @SerializedName("following_url")
    @Expose
    public String followingUrl;
    @SerializedName("gists_url")
    @Expose
    public String gistsUrl;
    @SerializedName("starred_url")
    @Expose
    public String starredUrl;
    @SerializedName("subscriptions_url")
    @Expose
    public String subscriptionsUrl;
    @SerializedName("organizations_url")
    @Expose
    public String organizationsUrl;
    @SerializedName("repos_url")
    @Expose
    public String reposUrl;
    @SerializedName("events_url")
    @Expose
    public String eventsUrl;
    @SerializedName("received_events_url")
    @Expose
    public String receivedEventsUrl;
    @SerializedName("type")
    @Expose
    public String type;
    @SerializedName("site_admin")
    @Expose
    public boolean siteAdmin;
    @SerializedName("name")
    @Expose
    public String name;
    @SerializedName("company")
    @Expose
    public Object company;
    @SerializedName("blog")
    @Expose
    public String blog;
    @SerializedName("location")
    @Expose
    public String location;
    @SerializedName("email")
    @Expose
    public String email;
    @SerializedName("public_repos")
    @Expose
    public int publicRepos;
    @SerializedName("public_gists")
    @Expose
    public int publicGists;
    @SerializedName("followers")
    @Expose
    public int followers;
    @SerializedName("following")
    @Expose
    public int following;
    @SerializedName("created_at")
    @Expose
    public String createdAt;
    @SerializedName("updated_at")
    @Expose
    public String updatedAt;

    public String getLogin() {
        return login;
    }

    public int getId() {
        return id;
    }
    ..........

GitHubAppApi.java

public interface GitHubAppApi {
    String SERVICE_ENDPOINT = AppConfig.BASE_URL;

    @GET("/repositories")
    Observable<ArrayList<GitHubRepo>> getRepositories();

    @GET("/users/{id}")
    Observable<GitUser> getUser(@Path("id") String userId);
}

Bên dưới là screenshot sau khi chạy sample: device-2015-11-23-084031.png

0