-
Notifications
You must be signed in to change notification settings - Fork 545
Design of RoboSpice
RoboSpice consists of different modules that are organized in layers, like an onion :
The very inner core of RoboSpice is made up of RoboSpice Cache. This module offers persistency to POJOs. It can simply save them in any format and load them from any format. Of course, this behavior is defined in the abstract class ObjectPersister, but it is possible to extend it to support any kind of persistency.
We will see that extensions of RoboSpice offer various implementation of the class ObjectPersister. The RoboSpice Cache also provides a few of them, for saving and loading data as :
- simple strings in text files
- binary data using java.io.Inputstream
- big binaries using java.io.Inputstream is alternative when you deal with larger data (e.g. bigger images), it will reduce memory consumption.
From a software design point of view, the RoboSpice Cache module implements the Chain Of Responsibility design pattern:
- The CacheManager holds an ordered list of ObjectPersister instances.
- Every ObjectPersister instance knows the POJO class it can save and it will be responsible for saving and loading it.
- When the CacheManager is asked to load or save some POJO, it will be passed 2 parameters:
- a CacheKey
- a POJO Class
- With that information, the CacheManager will ask all ObjectPersisters whether they can handle the required class.
- If so, the ObjectPersister will load or save it as appropriate.
- If no ObjectPersister inside the CacheManager can persist that Class, it will be logged.
Suppose you use REST requests: you request POJOs of different classes, and the results of the requests are all formatted using JSON. You want the CacheManager to save those data. But it would be suboptimal to add an ObjectPersister for each class of POJO you are interested in. Moreover those persisters are going to load and save the data exactly in the same way for each class.
The ObjectPersisterFactory class comes to the rescue. Objects of this class can create ObjectPersister instances on demand for a specific class. You can add ObjectPersisterFactory instances to the CacheManager Chain Of Responsability in the same way as as you can add ObjectPersister instances because both classes implement the Persister interface.
You can control the set of classes an ObjectPersisterFactory can handle via its constructor. By default, every ObjectPersisterFactory instance will try to handle all classes and create an ObjectPersister for them.
This module is the conceptual core of RoboSpice. This is the module that contains everything related to
- executing requests
- using an Android Service to process requests
- getting request results or errors back to the main thread.
Historically, RoboSpice was designed to handle network requests. Nevertheless, we quickly realized that it was indeed a good idea to execute every long running background task inside an Android Service. Networking is one long (or potentially long) process, but RoboSpice offers a solution for other long-running processess, such as data access or time-consuming computations. The advantages of using a Service remain exactly the same.
RoboSpice can be used in a totally network-unaware mode. We provide a clear sample for this case in samples Section.
By default, as explained, Robospice has been designed to ease network processing. Thus, every application using RoboSpice, unless it explicitly uses non-default NetworkStateChecker (see above), will have to declare certain permissions in its AndroidManifest.xml file:
- permission to use internet: android.permission.INTERNET
- permission to query the state of the network: android.permission.ACCESS_NETWORK_STATE
RoboSpice Core Module doesn't rely on any particular network protocol. Rather, it has been designed to support all of them and is not tied to a single one. This is true for all TCP/IP layer. Its versatility depends on this feature. That's the reason why RoboSpice will never deal with HTTP error codes.
Nevertheless, the extensions provide various mechanisms to deal with specific network protocols. They provide out-of-the-box support for advanced object serialization and deserialization from and to the network in formats such as JSON, XML and others.
A SpiceRequest is a basic RoboSpice object that performs the actual background task. Developers will subclass this class and override the loadDataFromNetwork() method and "do their stuff" there.
SpiceRequest instances will be processed inside an Android Service. In other words, the loadDataFromNetwork() method will be called inside by Android Service. Request processing is multi-threaded on RoboSpice.
SpiceRequest is a generic class. You will have to parameterize it with the type of the class that you want loadDataFromNetwork() to return. For instance, a SpiceRequest subclass that fetches tweets from Twitter will be declared as extending SpiceRequest<Tweet>.
The result returned by loadDataFromNetwork() is cached using the RoboSpice Cache module (see above). When an application executes a SpiceRequest, it can determine whether or not cached will be used, by passing a "maximum expiry date." If the cache contains no sufficiently recent data, then loadDataFromNetwork() will be called again (and the result stored in cache).
A SpiceRequest is identified inside RoboSpice by a "compound" key made of:
- a cacheKey given at execution
- the class of its result
A SpiceRequest can be cancelled at any time. RoboSpice will drop them off as soon as possible (and notify their listeners of their cancellation, see below).
While RoboSpice processes SpiceRequest instances inside a Service, applications will receive the result of the loadDataFromNetwork() method on the UI Thread.
RequestListener instances are basic RoboSpice objects that will get notified of the processing of a SpiceRequest. All notifications will occur on the UI Thread.
Typically, RequestListener are inner classes of the "launching context," that is, the Activity, Fragment (or Application or Service, etc.) that executes the SpiceRequest.
To create a RequestListener, create a new class that implements RequestListener parameterized with the type of object returned by a successful request, and that define two methods:
- one that will be called in case of success: onRequestSuccess()
- another that will be called in case of failure: onRequestFailure()
Eath methods will be passed an argument. In case of success, the argument will be the result of the SpiceRequest; on failure the argument will be an instance of SpiceException.
RequestProgressListener is a second interface that allows a RequestListener to be aware of the progress of the processing of a SpiceRequest.
Every application using Robospice has to declare (at least) a SpiceService in its AndroidManifest.xml file.
A SpiceService is an Android Service that can process any SpiceRequest instance. It owns a CacheManager, a NetworkStateChecker and a set of SpiceRequest instances to execute and their associated RequestListener instances.
It is responsible for :
- queueing requests
- processing them (using multi-threading). This involves
- checking if the result of SpiceRequest is stored by its CacheManager
- calling the loadFromNetwork of the SpiceRequest
- saving the result of SpiceRequest via its CacheManager
- notifying every RequestListener associated with the SpiceRequest
Extensions provide all setup SpiceService subclass that can be used out of-the-box to ease specific network data formats and cache them using these formats.
SpiceManager is a basic RoboSpice object that executes requests. SpiceManager instances are bound to "launching contexts:" every context (Activity, Fragment, etc.) that wants to execute SpiceRequests has to create its own SpiceManager and bind it to its life cycle.
The SpiceManager is aware of life cycle changes and, when the "launching context" gets destroyed, it will remove all RequestListener instances that would have notified the dying context. Thus SpiceManager instances have life cycles too.
From a software design point of view, SpiceManager instances are an asynchronous façade for using a SpiceService : they will bind to a SpiceService and ask it to effectively process every SpiceRequest and notify their RequestListeners. As binding to a service is asynchronous on Android, a SpiceManager will wait to be bound, queuing requests in the meantime, and pass them to the SpiceService when bound to it.
Due to the Architecture of Android, multiple SpiceManager instances can bind to a single shared SpiceService. If there is no SpiceService, a SpiceManager will start one when executing a first request.
Most extensions provide both additional persistence capacities to RoboSpice Cache Module and helpers to handle a specific network exchange formats like JSON (or XML or protobuf etc.).
The goal of extensions is to enhance existing adopted technologies and provide them with both caching and the ideal context of execution : an Android Service.
We consider extensions as extensions, and they are not the core of RoboSpice. The RoboSpice team is open to add new extensions if you want to contribute some.
This extension has it own wiki page.
This module is composed of :
- classes that extend SpiceRequest and SpiceService, helpers to create Spring Android queries.
- classes dedicated to persistence of Spring Android data formats.
This extension has it own wiki page.
This module is composed of :
- classes that extend SpiceRequest and SpiceService, helpers to create Google Http Java Client queries.
- classes dedicated to persistence of Google Http Java Client data formats.
This extension has it own wiki page.
This module is composed of :
- classes that extend SpiceRequest and SpiceService, helpers to create Retrofit queries.
- classes dedicated to persistence of Retrofit data formats.
This extension has it own wiki page.
This module is composed of :
- classes dedicated to persistence of POJOs inside a database using ORMLite.
This extension has it own wiki page.
This module is composed of :
- classes dedicated to display Android ListViews containing images loaded from the network.
Since release 1.4.0 of RoboSpice, we provide samples for all the core modules and extensions. Samples are located in the RoboSpice Samples repo on GitHub. They are standalone projects, as simple as possible to give examples of RoboSpice's modules usages.
We are open to adding new samples if you want to contribute one.
Samples work both with a maven/ADT configuration and an ant/ADT configuration.
RoboSpice uses 2 GitHub repositories :
- one for core and extensions modules
- the second one for the samples
This allows us to have a more modular permission approach for contributors and follows elegantly our maven architecture.
All modules are composed of :
- a parent artefact that manages dependencies and plugins specific to a module
- a child artefact for the module himself (a jar)
- a child artefact for testing the module
All parent artefacts have a common parent artefact that manages dependencies and plugins commons to modules.
Naming convention of the modules and artefact is strict since 1.4.0 and will not evolve for 1.X further releases. For historic reasons, the RoboSpice Core maven artefact is not called robospice-core but robospice.
Core and extensions modules can be built separately, with or without tests, and altogether.
Samples, on the other hands, don't constitute a whole but are strictly autonomous projects and maven artefacts (that should never get deployed to global maven repositories). Samples have been designed to demonstrate both RoboSpice programmatic usage and dependency management using both maven and ant.
RoboSpice has over 200 tests. The RoboSpice team is focused on testing and testability. Robustness is our main goal.
RoboSpice is under Continuous Integration at CloudBees. All builds are monitored under PMD + Findbugs + Checkstyle to ensure maximum quality.
We are open to new testing techniques, new tests and new quality analysis tools or peer reviews.