Store
A Store is responsible for managing a particular data request in your application. When you create a Store, you provide it with an implementation of a Fetcher class which defines how it will fetch new data. You can also define how your Store will save data in-memory and on-disk as well as how to parse it. Once you’ve defined how your Store will handles these actions, Store handles the logic around data flow, allowing your views to show the best data source and ensuring that the newest data is always available for later offline use. Stores can both be used with logical defaults or are fully customizable to work with SqlLite, and your favorite networking libraries
Store’s leverage RxJava and intelligent in-flight logic to prevent excessive calls to the network and disk cache. By wrapping your data calls in Store’s, you eliminate the possibility of flooding your network with the same request while adding 2 layers of caching (memory + disk)
Creating a Store
The simplest way to make a Store is with a builder:
Store<String> StringStore = StoreBuilder.<String>builder()
.nonObservableFetcher(barCode -> api.getArticle(barcode.getValue()))
.open();
Stores use Barcodes to identify data
Barcode barcode = new Barcode("Article", "42");
Using Stores
store.get(barCode).subscribe(observer)
will return cached data if it is available
store.fresh(barCode).subscribe(observer)
will call the fetcher skipping memory and disk cache
store.stream(barcode).subscribe(observer)
will call store.get but stay subscribed for any other store emissions (for any barcode)
Caching Policy Be default 100 items will be cached in memory for 24 hours. You may pass in your own instance of a guava Cache to override the default policy.
Adding a parser
Store<String> Store = ParsingStoreBuilder.<BufferedSource, String>builder()
.nonObservableFetcher(barCode -> source)) //okhttp responseBody.source()
.parser(source -> {
try (InputStreamReader reader = new InputStreamReader(source.inputStream())) {
return gson.fromJson(reader, String.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.open();
Middleware - GsonSourceParser We also are releasing a separate middleware lib with parsers to help in cases where your fetcher is a Reader, BufferedSource or String and your parser is Gson: GsonReaderParser, GsonSourceParser, GsonStringParser.
Our example can now be rewritten as:
```
Store<String> Store = ParsingStoreBuilder.<BufferedSource, String>builder()
.nonObservableFetcher(this::getResponse)
.parser(new GsonSourceParser<>(gson, String.class))
.open();
Disk Caching:
Stores can enable disk caching by passing in a Persister to the builder. Whenever a new network request is made, it will first write to the disk cache and then read from the disk cache.
Store<String> Store = ParsingStoreBuilder.<BufferedSource, String>builder()
.nonObservableFetcher(this::ResponseAsSource) //okhttp responseBody.source()
.persister(new Persister<BufferedSource>() {
@Override
public Observable<BufferedSource> read(BarCode barCode) {
if(dataIsCached)
return Observable.fromCallable(()->userImplementedCache.get(barcode));
Else
{
Return Observable.empty();
}
@Override
public Observable<Boolean> write(BarCode barCode, BufferedSource source) {
userImplementedCache.save(barcode,source)
return Observable.just(true);
}
})
.parser(new GsonSourceParser<>(gson, String.class))
.open();
Middleware - SourcePersister & FileSystem
At NYTimes we find streaming network responses directly to disk to be the fast form of persistence . As a result we have included in the Middleware a blazing fast reactive FileSystem which depends on OKIO BufferedSources. We have also included a SourcePersister which will give you disk caching with 1 line of code and works beautifully with GsonSourceParser
Store Store = ParsingStoreBuilder.<BufferedSource, String>builder() .nonObservableFetcher(this::ResponseAsSource) //okhttp responseBody.source() .persister(new SourcePersister(new FileSystemImpl(context.getFilesDir()))) .parser(new GsonSourceParser<>(gson, String.class)) .open();
Factory Classes:
You can also make a store in the following manners:
Store<String> simpleStore = new RealStore<>(StoreFactory.of(fetcher, persister));
Store<String> simpleStore = new RealStore<>(StoreFactory.withParser(fetcher, persister,parser));
Subclassing a Store
We can also subclass a Store implementation (RealStore<T>) passing a factory method to the parent
public class SampleStore extends RealStore<String> {
public SampleStore(Fetcher<String> f, Persister<String> p) {
super(StoreFactory.of(f, p));
}
}
Or with a parser:
public class SampleStore extends RealStore<String> {
public SampleStore(Fetcher<BufferedSource> f,
Persister<BufferedSource> p,
Parser<BufferedSource,String> parser) {
super(StoreFactory.withParser(f, p, parser);
}
}
Subclassing is useful for when you’d like to inject Store dependencies or add a few helper methods to a store ie:
public class SampleStore extends RealStore<String> {
@Inject
public SampleStore(Fetcher<String> f, Persister<String> p) {
super(StoreFactory.of(f, p));
}
}
Artifacts:
Since our stores depend heavily on Guava for Caching we have included 2 seperate artifacts:
Store-Base contains only Store classes and will need you to add RxJava + Guava to your code base. We strongly recommend proguard to strip out unused methods as guava has a large method count (insert how many methods). Store base is only 500 total methods.
Store-All Contains Store and guava shaded dependency (V19 currently). We have proguarded out all parts of guava that we are not using which takes the method count down to under 1000 guava methods. You will still need to add RxJava as a dependency to your app
Store-Middleware
Contains common implementation of Parser and Persister, ideally you would use the GsonSourceParser with the SourcePersister:
GsonReaderParser
GsonSourceParser
GsonStringParser
SourceFileReader
SourceFileWriter
SourcePersister
Middleware has a transitive dependency on GSON & OKIO
We’ve also include sample projects of how to use stores with:
OKHTTP + our FileSystem + GSON
Retrofit + FileSystem
Retrofit + SqlBrite + SqlDelight