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.

8bf455333a6db6cd3baceca7f90565ca?s=128

Natalya Blanco López

September 04, 2018
Tweet

Transcript

  1. The Tracking Problem: An Architectural approach to multi-process Synchronisation with

    RxJava Natalya Blanco López Mobile Developer @
  2. Natalya Blanco López Software Engineer - Venezuela Mobile Developer @

    Ubilabs
  3. Agenda

  4. • 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
  5. Multi-Process Synchronization

  6. 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
  7. The Tracking Problem

  8. 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
  9. 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
  10. Architectural Design Overview

  11. 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
  12. 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() }
  13. 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
  14. 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.
  15. 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.
  16. 2 3 6 4 5 7 8 1 9 10

    Architectural Design Overview
  17. Le Code

  18. Le Code

  19. 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) } } )
  20. 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>
  21. 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) ));
  22. 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); }
  23. 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
  24. 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") ) )
  25. 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) }
  26. 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
  27. Conclusion

  28. Conclusion

  29. 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
  30. Juliusstraße 25 22769 Hamburg Deutschland blanco@ubilabs.net +49 40 60 94

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