Skip to content

Instantly share code, notes, and snippets.

@digitalbuddha
Last active October 3, 2016 15:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save digitalbuddha/172214ed3af4ffe8479145225fa151d2 to your computer and use it in GitHub Desktop.
Save digitalbuddha/172214ed3af4ffe8479145225fa151d2 to your computer and use it in GitHub Desktop.
In Progress readme for new store Lib

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment