Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Meetup Multi-process synchronisation with RxJava

Android Meetup Multi-process synchronisation with RxJava

Sharing my experience on designing a solution to deal with the Producer-Consumer Problem using RxJava.

Natalya Blanco López

September 04, 2018

Other Decks in Programming


  1. • What is multi-process synchronisation? • Description of the problem.

    • Architectural approach to solve the problem. • A bit of code RxJava (not live coding - Sorry!) • Conclusion. Agenda
  2. The Producer's sole function is to insert data into the

    data-area, it is not allowed to remove any data from the area. Similarly, for the Consumer to be able to remove information from the data area, there must be information there in the first place. Once again, the sole function of the Consumer is to remove data from the data area. http://www.dcs.ed.ac.uk/home/adamd/essays/ex1.html “This problem is one ... well-known problems in concurrent programming: a finite-size buffer and two classes of threads, producers and consumers, put items into the buffer (producers) and take items out of the buffer (consumers).” https://docs.oracle.com/cd/E19455-01/806-5257/sync-31/index.html Multi-process synchronization
  3. We want to be able to visualise in real-time the

    position of certain devices on a map. This included several devices under different and sometimes problematic conditions: - Internet connection loss - GPS loss The Tracking Problem
  4. Things to consider: • We needed to provide accurate positional

    information: even when the GPS doesn't work as expected. • Consider possible connection loss due bad reception or no connection to the server. • We needed to make sure that the data received by the server was complete and in the correct order. • Handle all possible failures. The Tracking Problem
  5. Position Producer Service: generates positions every 2 seconds even when

    there's no more GPS available (or not accurate enough). Position Consumer Service: fetches from the database all the positions that haven’t been sent yet, makes sure that they are sorted and executes the request. Handles possible failures with the sending. Position Interpolator Service: makes sure all positions have a location that can be mapped in the server. Linear Interpolation Architectural Design Overview
  6. class Position { var sequenceNumber: Int = 0 var createdAt:

    Long = DateTime.now().millis var distance: Float = 0f var isTransmitted: Boolean = false var geoLocation: GeoLocation? = null var notes: List<Note> = List() }
  7. Producer Architectural Design Overview Every certain time generates positions even

    if there are incomplete. - Poor accuracy (>20m) - No GPS All the positions should be saved locally on the phone: - cope with internet connection loss - somebody has to fix those incomplete position
  8. Consumer Architectural Design Overview This component works based on a

    number to keep the correct order. After the positions are sent they are then marked as 'transmitted'. If the position with the upcoming sequence number is not complete, the consumer should wait until the interpolator does its job, and send the position.
  9. Interpolator Architectural Design Overview If the GPS doesn't provide a

    location, the interpolator comes to the rescue. we always want to have an idea of where the devices are. This component checks the database looking for incomplete positions and does a linear Interpolation with last valid position and the next valid.
  10. 2 3 6 4 5 7 8 1 9 10

    Architectural Design Overview
  11. Le Code: Producer LocationEngine: fusedLocationProviderClient.requestLocationUpdates(createLocationRequest(), locationCallback, null) LocationProducerService: compositeDisposable.add(Observable .interval(POSITION_PRODUCER_INTERVAL,

    TimeUnit.MILLISECONDS) .subscribe { time -> if (locationChanged(lastLocation, locationEngine.location!!)) { lastLocation = locationEngine.location sendLocationBroadcast(lastLocation) } else { sendLocationBroadcast(null) } } )
  12. Le Code: Producer PositionProducer: fun update(location: Location?) { compositeDisposable.add(Single.just(Position(location)) .flatMap

    { position -> repository.savePosition(position) .map { return@map position } }.subscribe( { position -> Timber.d("Position ${position.sequenceNumber} saved") }, { throwable -> Timber.d("error saving position: $throwable") } ) ) } Repository: fun savePosition(position: Position): Single<Position>
  13. Le Code: Consumer PositionConsumer: compositeDisposable.add(fetchAndSendPositionsToServer() .delay(2, TimeUnit.SECONDS) .repeat() .subscribe( success

    -> Timber.d("Positions sent?“ + success), throwable -> Timber.e("error sending position: " + throwable) ));
  14. Le Code: Consumer PositionConsumer: fun fetchAndSendPositionsToServer(): Single<Boolean> { return positionRepo.getSequenceNumberOfLastTransmittedPosition()

    .onErrorReturn(throwable -> { if (throwable instanceof IllegalStateException) { return 0; } throw new Exception(throwable); }) .flatMap(this::fetchAndBuildPositionDTOList) .flatMap(this::sendPositionsToServer) .doOnError(throwable -> { Timber.e(throwable);}) .onErrorReturnItem(false); }
  15. PositionConsumer: fun sendPositionsToServer(positionDTOs: List<PositionDTO>): Single<Boolean> { if (positionDTOs.empty) { return

    Single.just(false) } return api.sendPositions(positionDTOs) // Return Single<ResponseBody> .toObservable() .flatMapIterable(responseBody -> list) .flatMap(positionDTO -> positionRepo.markAsTransmitted( positionDTO.sequenceNumber) .toObservable()) .all(success -> success) } Le Code: Consumer
  16. Le Code: Interpolator PositionInterpolator: compositeDisposable.add(interpolatePositions() .delay(2, TimeUnit.SECONDS) .repeat(); .subscribe( success

    -> Timber.d("positions interpolated? " + success), throwable -> Timber.d("positions could not be interpolated: $throwable") ) )
  17. Le Code: Interpolator PositionInterpolator: fun interpolatePositions(): Single<Boolean> { return positionRepo.getFirstInvalidPositionAfter(startNumber)

    .flatMap(startPosition -> positionRepo.getValidPositionAfter(startPosition.sequenceNumber) .flatMap(endPosition -> positionRepo.getPositionsBetween( startPosition.sequenceNumber - 1, endPosition.sequenceNumber) ) ) .doOnError(throwable -> Timber.e("No interpolation " + throwable)) .flatMap(this::interpolate) // set to startNumber = endPosition.sequenceNumber .onErrorReturn(throwable -> false) }
  18. interval(): create an Observable that emits a sequence of integers

    spaced by a given time interval all(): Returns an Observable that emits a Boolean that indicates whether all of the items emitted by the source Observable satisfy a condition. http://reactivex.io/RxJava/javadoc/index.html?rx/Observable.html
  19. Conclusion Wrap-up: - With RxJava: - Easily define loops: repeat

    / retry (error) - Handle errors: doOnError / onErrorReturn - Three independent components: - Separate concerns - Work asynchronously - Write test more easily - Code looks clean and tidy - Code is easy to read
  20. Juliusstraße 25 22769 Hamburg Deutschland [email protected] +49 40 60 94

    661-0 www.ubilabs.net Natalya Blanco López Thank you!