谷歌浏览器
如果您还没有看过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
谷歌浏览器