/* * Copyright(c) 2017 lizhaotailang * * 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. */ package io.github.marktony.espresso.data.source.remote; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.List; import io.github.marktony.espresso.data.Package; import io.github.marktony.espresso.data.source.PackagesDataSource; import io.github.marktony.espresso.retrofit.RetrofitClient; import io.github.marktony.espresso.retrofit.RetrofitService; import io.reactivex.Observable; import io.reactivex.ObservableSource; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.functions.Predicate; import io.reactivex.schedulers.Schedulers; import io.realm.Realm; import io.realm.RealmConfiguration; import static io.github.marktony.espresso.realm.RealmHelper.DATABASE_NAME; /** * Created by lizhaotailang on 2017/3/7. * Implementation of the data source that adds a latency simulating network. */ public class PackagesRemoteDataSource implements PackagesDataSource { @Nullable private static PackagesRemoteDataSource INSTANCE; // Prevent direct instantiation private PackagesRemoteDataSource() { } // Access this instance for outside classes. public static PackagesRemoteDataSource getInstance() { if (INSTANCE == null) { INSTANCE = new PackagesRemoteDataSource(); } return INSTANCE; } // Destroy the instance. public static void destroyInstance() { INSTANCE = null; } @Override public Observable<List<Package>> getPackages() { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source return null; } @Override public Observable<Package> getPackage(@NonNull String packNumber) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source return null; } @Override public void savePackage(@NonNull Package pack) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source } @Override public void deletePackage(@NonNull String packageId) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source } /** * Update and save the packages' status by accessing the Internet. * @return The observable packages whose status are the latest. */ @Override public Observable<List<Package>> refreshPackages() { // It is necessary to build a new realm instance // in a different thread. Realm realm = Realm.getInstance(new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .name(DATABASE_NAME) .build()); return Observable.fromIterable(realm.copyFromRealm(realm.where(Package.class).findAll())) .subscribeOn(Schedulers.io()) .flatMap(new Function<Package, ObservableSource<Package>>() { @Override public ObservableSource<Package> apply(Package aPackage) throws Exception { // A nested request. return refreshPackage(aPackage.getNumber()); } }) .toList() .toObservable(); } /** * Update and save a package's status by accessing the network. * @param packageId The package's id or number. See {@link Package#number} * @return The observable package of latest status. */ @Override public Observable<Package> refreshPackage(@NonNull String packageId) { // It is necessary to build a new realm instance // in a different thread. Realm realm = Realm.getInstance(new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .name(DATABASE_NAME) .build()); // Set a copy rather than use the raw data. final Package p = realm.copyFromRealm(realm.where(Package.class) .equalTo("number", packageId) .findFirst()); // Access the network. return RetrofitClient.getInstance() .create(RetrofitService.class) .getPackageState(p.getCompany(), p.getNumber()) .filter(new Predicate<Package>() { @Override public boolean test(Package aPackage) throws Exception { return aPackage.getData() != null && aPackage.getData().size() > p.getData().size(); } }) .subscribeOn(Schedulers.io()) .doOnNext(new Consumer<Package>() { @Override public void accept(Package aPackage) throws Exception { // To avoid the server error or other problems // making the data in database being dirty. if (aPackage != null && aPackage.getData() != null) { // It is necessary to build a new realm instance // in a different thread. Realm rlm = Realm.getInstance(new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .name(DATABASE_NAME) .build()); // Only when the origin data is null or the origin // data's size is less than the latest data's size // set the package unread new(readable = true). if (p.getData() == null || aPackage.getData().size() > p.getData().size()) { p.setReadable(true); p.setPushable(true); p.setState(aPackage.getState()); } p.setData(aPackage.getData()); // DO NOT forget to begin a transaction. rlm.beginTransaction(); rlm.copyToRealmOrUpdate(p); rlm.commitTransaction(); rlm.close(); } } }); } @Override public void setAllPackagesRead() { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source } @Override public void setPackageReadable(@NonNull String packageId, boolean readable) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source } @Override public boolean isPackageExist(@NonNull String packageId) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source return false; } @Override public void updatePackageName(@NonNull String packageId, @NonNull String name) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source } @Override public Observable<List<Package>> searchPackages(@NonNull String keyWords) { // Not required because the {@link PackagesRepository} handles the logic // of refreshing the packages from all available data source return null; } }