diff --git a/README.md b/README.md index 1d9f8d7..e498bfc 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,27 @@ ![Downloads](https://jitpack.io/v/VictorAlbertos/RxCache/month.svg) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-RxCache-green.svg?style=true)](https://android-arsenal.com/details/1/3016) - # RxCache +## Fork Change Log +- Fix EvictProvider do not work. +## ------------ -> [**中文文档**](http://www.jianshu.com/p/b58ef6b0624b) +> [**中文文档1**](http://www.jianshu.com/p/b58ef6b0624b), +> [**中文文档2**](https://github.com/BryceLee/RxCache/blob/2.x/README_ZH.md) _For a more reactive approach go [here](https://github.com/VictorAlbertos/ReactiveCache/tree/2.x)_. -The **goal** of this library is simple: **caching your data models like [Picasso](https://github.com/square/picasso) caches your images, with no effort at all.** +The **goal** of this library is simple: **caching your data models like [Picasso](https://github.com/square/picasso) caches your images, with no effort at all.** Every Android application is a client application, which means it does not make sense to create and maintain a database just for caching data. -Plus, the fact that you have some sort of legendary database for persisting your data does not solves by itself the real challenge: to be able to configure your caching needs in a flexible and simple way. +Plus, the fact that you have some sort of legendary database for persisting your data does not solves by itself the real challenge: to be able to configure your caching needs in a flexible and simple way. -Inspired by [Retrofit](http://square.github.io/retrofit/) api, **RxCache is a reactive caching library for Android and Java which turns your caching needs into an interface.** +Inspired by [Retrofit](http://square.github.io/retrofit/) api, **RxCache is a reactive caching library for Android and Java which turns your caching needs into an interface.** -When supplying an **`observable`, `single`, `maybe` or `flowable` (these are the supported Reactive types)** which contains the data provided by an expensive task -probably an http connection, RxCache determines if it is needed +When supplying an **`observable`, `single`, `maybe` or `flowable` (these are the supported Reactive types)** which contains the data provided by an expensive task -probably an http connection, RxCache determines if it is needed to subscribe to it or instead fetch the data previously cached. This decision is made based on the providers configuration. - + ```java Observable> getMocks(Observable> oMocks); ``` @@ -38,21 +41,23 @@ allprojects { And add next dependencies in the build.gradle of the module: ```gradle dependencies { - compile "com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x" - compile "io.reactivex.rxjava2:rxjava:2.1.6" + implementation 'com.github.BryceLee.RxCache:runtime:2.0.0' + implementation "io.reactivex.rxjava2:rxjava:2.1.6" + //Optional,add annotationProcessor If you want to use Actionable: + annotationProcessor 'com.github.BryceLee.RxCache:compiler:2.0.0' } ``` Because RxCache uses internally [Jolyglot](https://github.com/VictorAlbertos/Jolyglot) to serialize and deserialize objects, you need to add one of the next dependency to gradle. - + ```gradle dependencies { - // To use Gson + // To use Gson compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.4' - + // To use Jackson compile 'com.github.VictorAlbertos.Jolyglot:jackson:0.0.4' - + // To use Moshi compile 'com.github.VictorAlbertos.Jolyglot:moshi:0.0.4' } @@ -67,26 +72,26 @@ interface Providers { @ProviderKey("mocks") Observable> getMocks(Observable> oMocks); - + @ProviderKey("mocks-5-minute-ttl") @LifeCache(duration = 5, timeUnit = TimeUnit.MINUTES) Observable> getMocksWith5MinutesLifeTime(Observable> oMocks); - + @ProviderKey("mocks-evict-provider") Observable> getMocksEvictProvider(Observable> oMocks, EvictProvider evictProvider); - + @ProviderKey("mocks-paginate") Observable> getMocksPaginate(Observable> oMocks, DynamicKey page); - + @ProviderKey("mocks-paginate-evict-per-page") Observable> getMocksPaginateEvictingPerPage(Observable> oMocks, DynamicKey page, EvictDynamicKey evictPage); - + @ProviderKey("mocks-paginate-evict-per-filter") Observable> getMocksPaginateWithFiltersEvictingPerFilter(Observable> oMocks, DynamicKeyGroup filterPage, EvictDynamicKey evictFilter); } ``` -RxCache exposes `evictAll()` method to evict the entire cache in a row. +RxCache exposes `evictAll()` method to evict the entire cache in a row. RxCache accepts as argument a set of classes to indicate how the provider needs to handle the cached data: @@ -259,30 +264,9 @@ Nevertheless, there are complete examples for [Android and Java projects](https: This actionable api offers an easy way to perform write operations using providers. Although write operations could be achieved using the classic api too, it's much complex and error-prone. Indeed, the [Actions](https://github.com/VictorAlbertos/RxCache/blob/master/runtime/src/main/java/io/rx_cache/ActionsList.java) class it's a wrapper around the classic api which play with evicting scopes and lists. -In order to use this actionable api, first you need to add the [repository compiler](https://github.com/VictorAlbertos/RxCache/tree/master/compiler) as a dependency to your project using an annotation processor. For Android, it would be as follows: - -Add this line to your root build.gradle: - -```gradle -dependencies { - // other classpath definitions here - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - } -``` - - -Then make sure to apply the plugin in your app/build.gradle and add the compiler dependency: - -```gradle -apply plugin: 'com.neenbedankt.android-apt' - -dependencies { - // apt command comes from the android-apt plugin - apt "com.github.VictorAlbertos.RxCache:compiler:1.8.3-2.x" -} -``` +In order to use this actionable api, first you need to add the [repository compiler](https://github.com/BryceLee/RxCache/tree/2.x/compiler) as a dependency to your project using an annotation processor. -After this configuration, every provider annotated with [@Actionable](https://github.com/VictorAlbertos/RxCache/blob/master/runtime/src/main/java/io/rx_cache/Actionable.java) `annotation` +Every provider annotated with [@Actionable](https://github.com/VictorAlbertos/RxCache/blob/master/runtime/src/main/java/io/rx_cache/Actionable.java) `annotation` will expose an accessor method in a new generated class called with the same name as the interface, but appending an 'Actionable' suffix. The order in the params supplies must be as in the following example: diff --git a/android/build.gradle b/android/build.gradle index 82f5150..4cca066 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -18,7 +18,7 @@ android { defaultConfig { applicationId "victoralbertos.io.android" minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 22 versionCode 1 versionName "1.0" } @@ -44,6 +44,27 @@ dependencies { annotationProcessor "com.google.dagger:dagger-compiler:2.14.1" compile "com.google.dagger:dagger:2.14.1" provided "org.glassfish:javax.annotation:10.0-b28" + + implementation(rootProject.ext.dependencies["retrofit-converter-gson"]) { + exclude module: 'gson' + exclude module: 'okhttp' + exclude module: 'okio' + exclude module: 'retrofit' + } + implementation(rootProject.ext.dependencies["retrofit-adapter-rxjava2"]) { + exclude module: 'rxjava' + exclude module: 'okhttp' + exclude module: 'retrofit' + exclude module: 'okio' + } + api rootProject.ext.dependencies["okhttp3"] + api 'com.squareup.okhttp3:logging-interceptor:3.5.0' + api(rootProject.ext.dependencies["retrofit"]) { + exclude module: 'okhttp' + exclude module: 'okio' + } + + api rootProject.ext.dependencies["rxandroid2"] } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index c1b3d44..bfbdff1 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ - + + + >> getUsers(Observable> users, DynamicKey idLastUserQueried, EvictProvider evictProvider); + @ProviderKey("mocks2") + @LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES) + Observable>> getUsers2(Observable> users, DynamicKey idLastUserQueried, EvictProvider evictProvider); +} diff --git a/android/src/main/java/victoralbertos/io/android/MainActivity.java b/android/src/main/java/victoralbertos/io/android/MainActivity.java index ba8eca2..f9c65b9 100644 --- a/android/src/main/java/victoralbertos/io/android/MainActivity.java +++ b/android/src/main/java/victoralbertos/io/android/MainActivity.java @@ -1,10 +1,264 @@ package victoralbertos.io.android; import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.Gson; + +import java.io.File; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; +import io.rx_cache2.DynamicKey; +import io.rx_cache2.EvictProvider; +import io.rx_cache2.Reply; +import io.rx_cache2.internal.RxCache; +import io.victoralbertos.jolyglot.GsonSpeaker; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; /** * Created by victor on 21/01/16. */ +/** + * TODO reappear steps: + * monitor Paging request! + * 1.onclick PageReFresh btn; + * 2.onclick LoadMore btn: + * i get some log: + * + * 08-14 16:17:35.297 25835-25854/victoralbertos.io.android D/okhttp_getUsers: lastIdQueried:1,source:CLOUD + * 08-14 16:17:37.990 25835-25853/victoralbertos.io.android D/okhttp_getUsers: lastIdQueried:20,source:CLOUD (loadmore has no data.so request from cloud is normal) + * + * 3.onclick PageReFresh btn; + * according to descriptions from readme-- EvictProvider allows to explicitly evict all the data associated with the provider. + * so the loadmore should request from cloud no matter EvictProvider.evict is true or no. + * + * but there are logs after onclick loadmore btn: + * D/okhttp_getUsers: lastIdQueried:20,source:PERSISTENCE + * + * so the is my pr reason. + * + * Thanks you read my pr! + */ public class MainActivity extends Activity { + String TAG="Rxtest"; + private Providers mProvider; + private RxCache rxCache; + private int lastid=1; + private TextView mtv; + private File externalCacheDir; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.layout); + mtv = (TextView) findViewById(R.id.tv); + HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); + httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);//ouput request basic message + OkHttpClient.Builder builder = new OkHttpClient.Builder().addNetworkInterceptor(httpLoggingInterceptor); + + Retrofit.Builder client = new Retrofit.Builder().baseUrl("https://api.github.com/").client(builder.build()); + client.addConverterFactory(GsonConverterFactory.create(new Gson())); + client.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); + File cacheDir = getFilesDir(); + new RxCache.Builder() + .persistence(cacheDir, new GsonSpeaker()) + .using(Providers.class); + Retrofit mretrofit = client.build(); + mProvider = mretrofit.create(Providers.class); + externalCacheDir = getExternalCacheDir(); + Log.d("Rxcache","externalCacheDir:"+ externalCacheDir.toString() ); + rxCache = new RxCache.Builder().persistence(externalCacheDir, new GsonSpeaker(new Gson())); + + } + public void ClearCache(View view){ + if (externalCacheDir!=null) { + deleteFile(externalCacheDir); + File[] files = externalCacheDir.listFiles(); + if (files==null){ + Toast.makeText(this,"delete:-1",Toast.LENGTH_LONG).show(); + return; + } + Toast.makeText(this,"delete:"+files.length,Toast.LENGTH_LONG).show(); + } + } + public void anotherProvider(View view){ + if (lastid==1){ + Toast.makeText(this,"Please onclick PageRefresh btn first",Toast.LENGTH_LONG).show(); + return; + } + Log.d(TAG, "LoadMore: "); + Observable.just(mProvider.getUsers(lastid,10)) + .flatMap(new Function>, ObservableSource>>() { + @Override + public ObservableSource> apply(Observable> listObservable) throws Exception { + return rxCache.using(CommonCache.class).getUsers2(listObservable,new DynamicKey(lastid),new EvictProvider(false)) + .map(new Function>, List>() { + @Override + public List apply(final Reply> listReply) throws Exception { + Log.d("okhttp_getUsers","lastIdQueried:"+lastid+",source:"+listReply.getSource()); + runOnUiThread(new Runnable() { + @Override + public void run() { + mtv.setText("lastIdQueried:"+lastid+",source:"+listReply.getSource()); + } + }); + return listReply.getData(); + } + }); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer>() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(List users) { + + } + + @Override + public void onError(Throwable e) { + Log.d("okhttp_Requesterror",e.toString()); + + } + + @Override + public void onComplete() { + + } + }); + } + private void deleteFile(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (int i = 0; i < files.length; i++) { + File f = files[i]; + boolean delete = f.delete(); + Log.d(TAG, "delete: "+delete); + } + } else if (file.exists()) { + } + } + public void PageReFresh(View view){ + lastid=1; + Log.d(TAG, "PageReFresh: "); + Observable.just(mProvider.getUsers(lastid,10)) + .flatMap(new Function>, ObservableSource>>() { + @Override + public ObservableSource> apply(Observable> listObservable) throws Exception { + return rxCache.using(CommonCache.class).getUsers(listObservable,new DynamicKey(lastid),new EvictProvider(true)) + .map(new Function>, List>() { + @Override + public List apply(final Reply> listReply) throws Exception { + Log.d("okhttp_getUsers","lastIdQueried:"+lastid+",source:"+listReply.getSource()); + runOnUiThread(new Runnable() { + @Override + public void run() { + mtv.setText("lastIdQueried:"+lastid+",source:"+listReply.getSource()); + } + }); + + return listReply.getData(); + } + }); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer>() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(List users) { + lastid = users.get(users.size() - 1).getId(); + } + + @Override + public void onError(Throwable e) { + Log.d("okhttp_Requesterror",e.toString()); + + } + + @Override + public void onComplete() { + + } + }); + + + } + + + public void LoadMore(View view){ + if (lastid==1){ + Toast.makeText(this,"Please onclick PageRefresh btn first",Toast.LENGTH_LONG).show(); + return; + } + Log.d(TAG, "LoadMore: "); + Observable.just(mProvider.getUsers(lastid,10)) + .flatMap(new Function>, ObservableSource>>() { + @Override + public ObservableSource> apply(Observable> listObservable) throws Exception { + return rxCache.using(CommonCache.class).getUsers(listObservable,new DynamicKey(lastid),new EvictProvider(false)) + .map(new Function>, List>() { + @Override + public List apply(final Reply> listReply) throws Exception { + Log.d("okhttp_getUsers","lastIdQueried:"+lastid+",source:"+listReply.getSource()); + runOnUiThread(new Runnable() { + @Override + public void run() { + mtv.setText("lastIdQueried:"+lastid+",source:"+listReply.getSource()); + } + }); + return listReply.getData(); + } + }); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer>() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(List users) { + + } + + @Override + public void onError(Throwable e) { + Log.d("okhttp_Requesterror",e.toString()); + + } + + @Override + public void onComplete() { + + } + }); + } } diff --git a/android/src/main/java/victoralbertos/io/android/Providers.java b/android/src/main/java/victoralbertos/io/android/Providers.java new file mode 100644 index 0000000..d44fd3c --- /dev/null +++ b/android/src/main/java/victoralbertos/io/android/Providers.java @@ -0,0 +1,16 @@ +package victoralbertos.io.android; + +import java.util.List; + +import io.reactivex.Observable; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.Query; + +public interface Providers { + String HEADER_API_VERSION = "Accept: application/vnd.github.v3+json"; + + @Headers({HEADER_API_VERSION}) + @GET("/users") + Observable> getUsers(@Query("since") int lastIdQueried, @Query("per_page") int perPage); +} diff --git a/android/src/main/java/victoralbertos/io/android/User.java b/android/src/main/java/victoralbertos/io/android/User.java new file mode 100644 index 0000000..4069bf2 --- /dev/null +++ b/android/src/main/java/victoralbertos/io/android/User.java @@ -0,0 +1,30 @@ +package victoralbertos.io.android; + +public class User { + private final int id; + private final String login; + private final String avatar_url; + + public User(int id, String login, String avatar_url) { + this.id = id; + this.login = login; + this.avatar_url = avatar_url; + } + + public String getAvatarUrl() { + if (avatar_url.isEmpty()) return avatar_url; + return avatar_url.split("\\?")[0]; + } + + + public int getId() { + return id; + } + + public String getLogin() { + return login; + } + + @Override public String toString() { + return "id -> " + id + " login -> " + login; + }} diff --git a/android/src/main/res/layout/layout.xml b/android/src/main/res/layout/layout.xml new file mode 100644 index 0000000..7d53baa --- /dev/null +++ b/android/src/main/res/layout/layout.xml @@ -0,0 +1,35 @@ + + + +