Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Recipes

Mike Nakhimovich edited this page Mar 23, 2017 · 20 revisions

Welcome to the Store Recipes!

Creating Stores

There are 3 entry points in StoreBuilder which can be used to construct a store. The differences in which one to use lie in whether you have your own Key Type and whether you will have a parser

public final class StoreBuilder {
...
    public static <Parsed> RealStoreBuilder<Parsed, Parsed, BarCode> barcode()

    public static <Key, Parsed> RealStoreBuilder<Parsed, Parsed, Key> key()

    public static <Key, Raw, Parsed> RealStoreBuilder<Raw, Parsed, Key> parsedWithKey()
...
}

Cached then fresh data

First call get and concat the result with fetch. Next add a distinct operator which will only allow unique emissions. If cache and network return same value or if the first get call actually needs to go to network, only 1 item will be emitted.

store.get(barCode)
                .concatWith(store.fetch(barCode))
                .distinct()
                .subscribe()

Note: If you omit the distinct() you will always received two values

Staying Subscribed for future emissions.

store.stream()
                .subscribe()
  • NOTE: Stream will continue to get emissions for ANY barcode. You can use filter operator to listen for subset of results.

Refresh when clear

Sometimes you'd like to fetch new data anytime your store is cleared. A common use case is wanting to rerequest data from network anytime a user submits a different POST request. the public store method getRefreshing(key) allows you to call store.clear(key) anytime you want observable to repeat (hit the network again). Example usage:

allNotesStore.getRefreshing(barCode)
                .subscribe(observer);

notesApi.submitNewNote(barcode);

allNotesStore.clear(barcode); //when clear is called store will hit network again

Refresh on Stale

When using a Persister you may want to backfill your disk cache anytime the record is stale. To do this add an additional method to your StoreBuilder and implement RecordProvider or use our built in RecordPersister store = StoreBuilder.barcode() .fetcher(fetcher) .persister(persister) .refreshOnStale() .open();

    Store<ArticleAsset, Integer> store = StoreBuilder.<ArticleAsset>barcode()
            .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(),articleBarcode.getType()))
          .persister(new RecordPersister(FileSystemFactory.create(context.getFilesDir()),5, TimeUnit.HOURS))
            .open();

Network on Stale

When using a Persister you may want to hit the network before returning stale disk data. To do this add an additional method to your StoreBuilder and implement RecordProvider or use our built in RecordPersister store = StoreBuilder.barcode() .fetcher(fetcher) .persister(persister) .networkBeforeStale() .open();

    Store<ArticleAsset, Integer> store = StoreBuilder.<ArticleAsset>barcode()
            .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(),articleBarcode.getType()))
          .persister(new RecordPersister(FileSystemFactory.create(context.getFilesDir()),5, TimeUnit.HOURS))
            .open();

Clearable disk cache

If you'd like store.clear(key) to also clear your disk cache, have your Persister implement Clearable

MultiParser

Sometimes you want to use a middleware parser to go from json to pojo and then use another parser to unwrap your data.

       Parser<BufferedSource, RedditData> sourceParser = GsonParserFactory.createSourceParser(provideGson(), RedditData.class);
       Parser<RedditData, Data> envelopeParser = redditData -> redditData.data();

       ParsingStoreBuilder.<BufferedSource,RedditData>builder()
               .fetcher(this::fetcher)
               .persister(persister)
               .parser(new MultiParser<>(Arrays.asList(sourceParser,envelopeParser)))
               .open();

Top Level JSON Array

In some cases you may need to parse a top level JSONArray, in which case you can provide a TypeToken.

Store<List<Article>> Store = ParsingStoreBuilder.<BufferedSource, List<Article>>builder()
                .nonObservableFetcher(this::getResponse)
                .parser(GsonParserFactory.createSourceParser(gson, new TypeToken<List<Article>>() {}))
                .open();

Custom Memory Caching Policy

By default stores will cache up to 100 items for 24 hours using an expireAfterAccess policy. You can pass in your own Cache instance to override the duration and type of caching policy. The below example will cache 1 item forever allowing you to keep the memory footprint of the store lower

ParsingStoreBuilder.<BufferedSource,RedditData>builder()
                .fetcher(this::fetcher)
                .parser(GsonParserFactory.createSourceParser(provideGson(),RedditData.class))
                .memory(CacheBuilder.newBuilder()
                        .maximumSize(1)
                        .expireAfterWrite(Long.MAX_VALUE, TimeUnit.SECONDS)
                        .build())
                .open();

Subclassing Stores

There are times when you'd like to build a store with a few additional helper methods. Subclassing stores is your best bet. Based on what you pass into the constructor will denote how store is configured

public class SampleStore extends RealStore<String, BarCode> {
   @Inject
   public SampleStore(Fetcher<String, BarCode> fetcher, Persister<String, BarCode> persister) {
        super(fetcher, persister);
    }
}

Disk Caching with FileSystemPersister

If you want to enable disk caching the easiest way is to use the Source Persister by adding
compile 'com.nytimes.android:filesystem:VERSION' to your build.gradle

A source persister can persist something that is a BufferedSource. Most easily you can get a BufferedSource from Okhttp Response or Retrofit ResponseBody. If using a different network client in your fetcher, you can create a BufferedSource with:

Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8))))

if coming from an inputStream

Okio.buffer(Okio.source(inputStream))

Once you have a fetcher that returns a BufferedSource you can make a FileSystemPersister Persister

Persister<BufferedSource, BarCode> persister = FileSystemPersister.create(fileSystem, key ->"Book"+key.getType()+key.getKey())

When using the FileSystemFactory you must supply a PathResolver. The reason for a PathResolver is to allow sharing of same FileSystem between multiple stores.

StoreBuilder.<BufferedSource>builder()
                .fetcher(this::fetcher)
                .persister(persister)
                .open();

Most use cases will want to parser the data into a pojo rather than returning a BufferedSource middleware parsers work perfectly with the SourcePersister

ParsingStoreBuilder.<BufferedSource,RedditData>builder()
                .fetcher(this::fetcher)
                .persister(persister)
                .parser(MoshiParserFactory.createSourceParser(new Moshi.Builder().build(),RedditData.class))
                .open();
                .parser(JacksonParserFactory.createSourceParser(new ObjectMapper(),RedditData.class))
                .parser(GsonParserFactory.createSourceParser(new Gson(),RedditData.class))