RxChange-Java is a library that implements the reactive change model for Java. It was designed to simplify the code required to update application components (e.g. UI, database) based on changes in data, while maintaining consistency and reliability.
Under the hood, the library uses RxJava for publishing and subscribing to change events and Guava to provide immutable snapshots for collections of data.
The architecture for the reactive change model consists of 3 main components:
- Data Layer
- Change Layer
- Message Layer
The data layer is where the underlying data is stored. This data will be accessed and modified throughout a program's lifespan.
It is important to note that the data cannot be modified directly with the reactive change model, as it must be done through the change layer.
The change layer is the layer in which the data is actually modified. An adapter is used by the caller to either retrieve or modify the underlying data. For each successful change in the data, a change message will be emitted to interested observers.
The reactive change model supports both individual changes (e.g. adding a single element in a list) and batch changes (e.g. adding a multiple elements at a time).
The message layer is where the change messages are emitted. It is also the layer where the observers subscribe to and act upon these change events. Each change message contains snapshots of the data (both before and after the change), the type of change that occurred, and the metadata pertaining to the change itself (e.g. the element that was added).
The reactive change model supports 3 types of data changes: add, remove, and update.
// For standard Java projects
implementation 'com.umbraltech:rxchange-java:x.y.z-jre'
// For Android projects
implementation 'com.umbraltech:rxchange-java:x.y.z-android'
<!-- For standard Java projects -->
<dependency>
<groupId>com.umbraltech</groupId>
<artifactId>rxchange-java</artifactId>
<version>x.y.z-jre</version>
</dependency>
<!-- For Android projects -->
<dependency>
<groupId>com.umbraltech</groupId>
<artifactId>rxchange-java</artifactId>
<version>x.y.z-android</version>
</dependency>
The following adapters are packaged with the RxChange-Java library. Information regarding supported operations can be seen in the table below.
Adapter | Corresponding Data | Change Operations | Metadata Available? |
---|---|---|---|
SingleChangeAdapter | Object | update (data: D) | No |
ListChangeAdapter | List | add (data: D) addAll (data: List) addAt (index: int, data: D) remove (data: D) removeAll (data: List) removeAt (index: int) update (index: int, data: D) |
Yes |
MapChangeAdapter | Map | add (key: K, data: D) addAll (entries: Map) remove (key: K) removeAll (keys: Set) update (key: K, data: D) updateAll (entries: Map) |
Yes |
SetChangeAdapter | Set | add (data: D) addAll (data: Set) remove (data: D) removeAll (data: Set) |
Yes |
In order to listen to change events, an observer must be registered with the adapter responsible for the data. The examples below include code for registering, filtering, and reading data from these change events.
To maintain simplicity, all the code samples will use a ListChangeAdapter
with data of type Integer
.
RxChange-Java uses RxJava for listening to change events. The sample code below demonstrates how an observer can be registered with a change adapter.
listChangeAdapter.getObservable()
.subscribe(changeMessage -> /* Logic */ );
The ChangeMessage
class provides 3 accessors:
getOldPayload()
- returns the data before the changegetNewPayload()
- returns the data after the changegetChangeType()
- returns the type of change that occurred with the data
The MetaChangeMessage
class is an extension of the ChangeMessage
class, and can be used to read the metadata corresponding to a change in data. In order to access the metadata, simply call getMetadata()
on a MetaChangeMessage
instance.
For the collections provided by the library, you can acquire access to the MetaChangeMessage
instance simply by casting or mapping like in the sample code below.
listChangeAdapter.getObservable()
.map(changeMessage -> (MetaChangeMessage<List<Integer>, Integer>) changeMessage)
.subscribe(metaChangeMessage -> /* Logic */ );
For more information on what metadata values are provided, please refer to the documentation for the adapters.
RxChange-Java comes with bundled with filters that can be used while registering observers, so that the code contained in the observers will only be triggered when certain conditions are met.
The ChangeTypeFilter
class allows for listening to changes of a specific type. The example below registers an observer whose logic is only invoked when data is added to the adapter.
listChangeAdapter.getObservable()
.filter(new ChangeTypeFilter(ChangeType.ADD))
.subscribe(changeMessage -> /* Logic */ );
The MetadataFilter
class allows executing logic only when certain types of metadata are provided.
Using metadata filters can be especially useful if an adapter supports performing both single and batch updates. Information on the types of metadata supported by the built-in adapters are mentioned in the documentation.
The examples below demonstrate how filtering can be done for both single and batch operations (for a list adapter).
listChangeAdapter.getObservable()
.map(changeMessage -> (MetaChangeMessage<List<Integer>, Integer>) changeMessage)
.filter(new MetadataFilter(Integer.class))
.subscribe(metaChangeMessage -> /* Logic */ );
listChangeAdapter.getObservable()
.map(changeMessage -> (MetaChangeMessage<List<Integer>, Integer>) changeMessage)
.filter(new MetadataFilter(List.class))
.subscribe(metaChangeMessage -> /* Logic */ );
The following example combines all of the segments listed above into a simple example that prints the values contained in each change message when data is added to the adapter:
Code:
final ListChangeAdapter<Integer> listChangeAdapter = new ListChangeAdapter<>();
listChangeAdapter.getObservable()
.filter(new ChangeTypeFilter(ChangeType.ADD))
.filter(new MetadataFilter(Integer.class))
.map(changeMessage -> (MetaChangeMessage<List<Integer>, Integer>) changeMessage)
.subscribe(metaChangeMessage -> {
System.out.println("---- Observer 1 (Single) ----");
System.out.println("Old List: " + metaChangeMessage.getOldData());
System.out.println("New List: " + metaChangeMessage.getNewData());
System.out.println("Metadata: " + metaChangeMessage.getMetadata());
});
listChangeAdapter.getObservable()
.filter(new ChangeTypeFilter(ChangeType.ADD))
.filter(new MetadataFilter(List.class))
.map(changeMessage -> (MetaChangeMessage<List<Integer>, List<Integer>>) changeMessage)
.subscribe(metaChangeMessage -> {
System.out.println("---- Observer 2 (Batch) ----");
System.out.println("Old List: " + metaChangeMessage.getOldData());
System.out.println("New List: " + metaChangeMessage.getNewData());
System.out.println("Metadata: " + metaChangeMessage.getMetadata());
});
// Add the single integer to the dataset
listChangeAdapter.add(1);
final List<Integer> batchIntegers = new ArrayList<>();
batchIntegers.add(2);
batchIntegers.add(3);
batchIntegers.add(4);
// Add the list of integers to the dataset
listChangeAdapter.addAll(batchIntegers);
Output:
---- Observer 1 (Single) ----
Old List: []
New List: [1]
Metadata: 1
---- Observer 2 (Batch) ----
Old List: [1]
New List: [1, 2, 3, 4]
Metadata: [2, 3, 4]
When developing Android applications, it may be the case that observers need to be aware of an Activity or Fragment's lifecycle. We recommend using the AutoDispose library to achieve this purpose.