谷歌浏览器

谷歌浏览器

如果您还没有看过Google的架构,则可以在此处了解更多信息。 另外,如果您不熟悉ObjectBox,请查看这篇文章

介绍

该体系结构的目标是最终得到如下结果:

主要区别在于,我将使用ObjectBox而不是Room。 该体系结构不强制执行任何特定的实现。 您可以随时交换实施细节。 我发现ObjectBox是最简单的数据库之一,它允许进行React式查询而无需依赖RxJava(尽管您可以使用RxJava)。

要查看完整的示例,您可以在此处找到GitHub存储库。

入门

首先,我向项目添加了两个模型:Zoo和Animal。 动物园与动物具有一对多关系。 您可以在下面查看其实现:

@Entity
public class Zoo {

    @Id
    private long id;
    private String name;

    @Backlink
    public ToMany<Animal> animals;

    public Zoo() {
    }

    public Zoo(String name) {
        this.name = name;
    }

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
@Entity
public class Animal {

    @Id
    private long id;

    private String name;
    private String image;
    private String group;

    public ToOne<Zoo> zoo;

    public Animal() {
    }

    public Animal(String name, String image, String group) {
        this.name = name;
        this.image = image;
        this.group = group;
    }

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }
}

然后,我创建了一个简单的MainActivity(“视图”层),其中的RecyclerView用于显示Zoos列表,而FloatingActionButton(FAB)用于添加新的Zoos。 我还为该示例添加了一些模拟数据,因此该应用程序将需要显示一些数据。 该活动的代码如下:

public class MainActivity extends AppCompatActivity {

    private ZooListViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.activity_main_recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        ZooAdapter adapter = new ZooAdapter();
        adapter.setOnClickListener((view, position) -> {
            Intent zooIntent = new Intent(MainActivity.this, ZooActivity.class);
            zooIntent.putExtra(ZooActivity.EXTRA_ZOO_ID, adapter.getItemId(position));
            startActivity(zooIntent);
        });
        recyclerView.setAdapter(adapter);

        mViewModel = ViewModelProviders.of(this).get(ZooListViewModel.class);
        mViewModel.getZoos().observe(this, (adapter::update));

        findViewById(R.id.activity_main_fab).setOnClickListener(v -> {
            DialogFragment zooFragment = ZooFragment.newInstance();
            zooFragment.show(getSupportFragmentManager(), ZooFragment.class.getName());
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mViewModel.refreshZoos();
    }
}

我为MainActivity创建ViewModel,它负责加载Zoo列表。 活动会观察此数据,并在对其进行更改时让ZooAdapter知道它。 我省略了适配器和布局的详细信息,因为它们只是常规实现。 如果您想查看适配器,可以在这里查看。

您可以在下面看到结果:

视图模型

ViewModel本质上仅负责存储库和视图之间的通信。 它将请求从视图发送到存储库,并将结果也返回到视图。 我尝试为每个视图制作一个ViewModel,但是如果可以的话,您可以使用更多的ViewModel。 我所有的ViewModel都从管理ObjectBox订阅的BaseViewModel类扩展而来:

public abstract class BaseViewModel extends ViewModel {

    private final List<DataSubscription> mSubscriptions;

    @Override
    protected void onCleared() {
        super.onCleared();
        for (DataSubscription subscription : mSubscriptions) {
            if (!subscription.isCanceled()) {
                subscription.cancel();
            }
        }
    }

    protected final void addSubscription(@NonNull DataSubscription subscription) {
        mSubscriptions.add(subscription);
    }

    public BaseViewModel() {
        mSubscriptions = new ArrayList<>();
    }
}

为了真正正确地遵循这种类型的体系结构,我不应该将ObjectBox暴露给ViewModel层。 我以这种方式保留了它,以使示例更简单,但是您可以使用RxRelay之类的东西将其抽象出来。

管理我的MainActivity的ViewModel看起来像这样:

public class ZooListViewModel extends BaseViewModel {

    private MutableLiveData<List<Zoo>> mZoosLiveData;

    public ZooListViewModel() {
        mZoosLiveData = new MutableLiveData<>();
        DataSubscription subscription = ZooRepository.subscribeToZooList(this::refreshZoos);
        addSubscription(subscription);
    }

    private void refreshZoos(List<Zoo> zoos) {
        mZoosLiveData.postValue(zoos);
    }

    public LiveData<List<Zoo>> getZoos() {
        return mZoosLiveData;
    }

    public void refreshZoos() {
        ZooRepository.refreshZoos();
    }
}

它使用MutableLiveData初始化自身以观察List

,以及一个ObjectBox DataSubscription。 此订阅监视数据库中的更改,只要数据更改,它将设置MutableLiveData。 然后它将通知自己的观察者该更改。 LiveData的一件好事是它的观察者知道生命周期,因此仅在视图处于活动状态时才发送后台数据更新。

它还公开了刷新Zoo的方法。 这将触发存储库刷新源中的数据。 通常是远程服务器,因此它将发送网络请求以获取最新数据。 结果,ObjectBox数据库得到更新,相关的订阅将得到通知。 结果导致我的ViewModel中的LiveData得到更新,将结果传递给其观察者,并通知视图层。

资料库

存储库负责将请求从ViewModel传递到数据库和网络,并返回响应。 我将这些层分为API类(用于访问远程API)和DAO类(用于访问数据库)。 您可以在此处查看我的实现:

public class ZooRepository {

    public static DataSubscription subscribeToZooList(DataObserver<List<Zoo>> observer) {
        return ZooDAO.subscribeToZooList(observer);
    }

    public static DataSubscription subscribeToZoo(DataObserver<Zoo> observer, long id, boolean singleUpdate) {
        return ZooDAO.subscribeToZoo(observer, id, singleUpdate);
    }

    public static void refreshZoo(long id) {
        ZooAPI.loadZoo(id, zooResponse -> {
            if (zooResponse != null && zooResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zooResponse.getPayload());
                parser.parseZoo();
                Zoo zoo = parser.getZoo();
                if (zoo != null) {
                    ZooDAO.insertZoo(zoo);
                }
            }
        });
    }

    public static void refreshZoos() {
        ZooAPI.loadZoos(zoosResponse -> {
            if (zoosResponse != null && zoosResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zoosResponse.getPayload());
                parser.parseZooList();
                List<Zoo> zoos = parser.getZooList();
                if (zoos != null) {
                    ZooDAO.insertZoos(zoos);
                }
            }
        });
    }

    public static void addZoo(Zoo newZoo, MutableLiveData<ZooUpdateResponse> liveResponse) {
        liveResponse.postValue(new ZooUpdateResponse(Response.STATUS_LOADING));
        ZooAPI.addZoo(newZoo, zooResponse -> handleZooResponse(zooResponse, liveResponse));
    }

    public static void updateZoo(Zoo zoo, MutableLiveData<ZooUpdateResponse> liveResponse) {
        liveResponse.postValue(new ZooUpdateResponse(Response.STATUS_LOADING));
        ZooAPI.updateZoo(zoo, zooResponse -> handleZooResponse(zooResponse, liveResponse));
    }

    private static void handleZooResponse(Response zooResponse, MutableLiveData<ZooUpdateResponse> liveResponse) {
        if (zooResponse != null) {
            if (zooResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zooResponse.getPayload());
                parser.parseZoo();
                Zoo zoo = parser.getZoo();
                if (zoo != null) {
                    ZooDAO.insertZoo(zoo);
                }
            }

            liveResponse.postValue(new ZooUpdateResponse(zooResponse.getStatus()));
        }
    }
}

我将大多数逻辑保留在存储库层中。 DAO和API层仅负责一项。 对于DAO,它与数据库交互,对于API,它与远程API交互。 他们要么传递数据,要么返回数据,他们自己不操纵数据。 因此,当存储库从API获得响应时,它负责解析该响应,然后将其发送到DAO以更新数据库。

如果为存储库提供了Observer或LiveData,它将在完成时设置其值,以将响应传递回ViewModel。 这些响应对象可以包含其他信息,例如状态代码(成功,失败等)和错误消息。 由于数据模型将不包含此类信息。 这些响应对象的实现实际上取决于ViewModel需要什么样的信息。

解析中

为了防止资源库过大,我将响应解析逻辑分为自己的一组类。 这些解析类仅采用响应字符串(可能是JSON),并将其转换为适当的模型。 为了解析Zoo响应,我的课程如下所示:

public class ZooParser {

    private String mResponse;
    private Zoo mZoo;
    private List<Zoo> mZooList;
    private List<Animal> mAnimalList;
    private Gson mGson;

    public ZooParser(String response) {
        mResponse = response;
        mGson = new Gson();
    }

    public void parseZooList() {
        if (mResponse != null) {
            Zoo[] zoos = mGson.fromJson(mResponse, Zoo[].class);
            mZooList = Arrays.asList(zoos);
        }
    }

    public void parseZoo() {
        if (mResponse != null) {
            mZoo = mGson.fromJson(mResponse, Zoo.class);
        }
    }

    public Zoo getZoo() {
        return mZoo;
    }

    public List<Zoo> getZooList() {
        return mZooList;
    }

    public List<Animal> getAnimalList() {
        return mAnimalList;
    }
}

您可能会注意到,我在解析器中包括了动物列表–这是因为对Zoo的响应也可能包含动物。 尽管在此示例中我实际上并未实现任何此类响应。 这里的想法是Parser类不会直接将JSON对象映射到我的数据模型。 它解析整个响应,其中可能包括多个数据模型或不属于模型的特定键(例如页数)。

该层负责与数据库进行交互。 我将为每个模型创建DAO类,例如ZooDAO:

public class ZooDAO {

    private static Box<Zoo> getZooBox() {
        BoxStore boxStore = App.getBoxStore();
        return boxStore.boxFor(Zoo.class);
    }

    public static DataSubscription subscribeToZooList(DataObserver<List<Zoo>> observer) {
        return getZooBox().query().build().subscribe().on(AndroidScheduler.mainThread()).observer(observer);
    }

    public static DataSubscription subscribeToZoo(DataObserver<Zoo> observer, long id, boolean singleUpdate) {
        SubscriptionBuilder<Zoo> builder = getZooBox().query().eager(Zoo_.animals).equal(Zoo_.id, id).build().subscribe().transform(list -> {
            if (list.size() == 0) {
                return null;
            } else {
                return list.get(0);
            }
        }).on(AndroidScheduler.mainThread());

        if (singleUpdate) {
            builder.single();
        }
        return builder.observer(observer);
    }

    public static void insertZoo(Zoo zoo) {
        getZooBox().put(zoo);
    }

    public static void insertZoos(Collection<Zoo> zoos) {
        getZooBox().put(zoos);
    }
}

在这里,您可以看到直接访问ObjectBox。 它执行所有必需的数据库操作,并建立查询订阅。 可以在ViewModel中观察到这些查询,该ViewModel会将任何数据更新通知LiveData。

API

API层用于向网络发出请求,并将这些响应传递回存储库。 本示例中的实现已被模拟,您可以在此处查看源代码。 这可以使用任何类型的网络请求,例如可以使用翻新

存储库观察来自API的响应并进行处理。 每个API请求都会创建一个Response对象:

public class Response {

    public static final int STATUS_LOADING = 0, STATUS_SUCCESS = 1, STATUS_FAIL = 2;

    @Retention(SOURCE)
    @IntDef({STATUS_LOADING, STATUS_SUCCESS, STATUS_FAIL})
    @interface Status {
    }

    private final int mStatus;
    private String mPayload;

    public Response(@Status int status, String payload) {
        mStatus = status;
        mPayload = payload;
    }

    @Status
    public int getStatus() {
        return mStatus;
    }

    public String getPayload() {
        return mPayload;
    }
}

这是存储库所需的相关信息-响应有效负载和状态。 可以添加更多状态,例如针对特定错误状态(如身份验证失败)。

汇集全部

在此示例中,我的MainActivity观察Zoos LiveData。 设置此值时将通知它。 例如,如果我添加一个新的Zoo,它将自动刷新视图。 这是因为我已经使用ObjectBox设置了DataSubscription来观察更改。 您可以在这里看到实际效果:

您可以在此处找到此DialogFragment的源代码。 此类的重要部分是在这里观察响应:

...

        ZooFragmentViewModel viewModel = ViewModelProviders.of(this).get(ZooFragmentViewModel.class);
        viewModel.getZooUpdateResponse().observe(this, response -> {
            if (response != null) {
                switch (response.getStatus()) {
                    case Response.STATUS_LOADING:
                        showProgressBar(true);
                        break;
                    case Response.STATUS_SUCCESS:
                        dismiss();
                        break;
                    case Response.STATUS_FAIL:
                        showProgressBar(false);
                        Toast.makeText(getContext(), response.getErrorMessage(), Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        });

...

在ViewModel中触发保存操作时,它将使用加载状态设置LiveData的响应。 然后,当成功或失败时,它也将结果设置回去。 一旦它返回MainActivity,您可以看到添加了新的Zoo,而不必显式触发刷新。

结语

要查看完整的示例,您可以在此处找到GitHub存储库。 但是,您可以将我在这里解释的内容应用于任何新的视图/模型/存储库。

我相信,与完全干净的体系结构相比,该体系结构更易于理解和应用。 但是,同样的原则适用–就像隔离唯一的层并赋予它们单一的责任一样。 我不认为我在这里介绍的内容是完整的,它只是一个起点。 您可以按原样使用它,但是您应该考虑如何构建和改进它。 目标是朝着Buffer的Clean Architecture Boilerplate之类迈进。 但是,在没有扎实基础的情况下掌握所有这些概念可能会令人不知所措。

另外,为了使操作更容易理解,我尝试将依赖关系降至最低。 我没有包括Retrofit,OkHttp,Butterknife,RxJava,Dagger等内容。您可以添加任何想要使用的库,也可以换出ObjectBox。 虽然我认为这种方法效果很好。

让我知道您对这种方法的看法。 另外,如果您有任何意见,问题或建议,请发表。

翻译自: https://www.javacodegeeks.com/2017/10/applying-googles-android-architecture-objectbox-database.html

谷歌浏览器

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐