Wednesday, June 24, 2020

Being Bad at Coroutines: Part 2 Electric Boogaloo

    Cachify and Highlander recently received updates to 0.0.14 and 0.0.12 respectively and bring with them some changes that should help improve performance. In case you've forgotten, Cachify is a Kotlin coroutine powered implementation of the launch-or-join pattern, and Highlander is a Kotlin coroutine powered implementation of the cancel-then-run pattern. Cachify also implements simple in-memory caching of its results, though this is optional.

    Before, both of these libraries internally relied on a mutex to lock a common piece of data while it was being manipulated - but this mutex lock was actually causing the libraries to be less efficient due to how the CoroutineScope was being manipulated. This has been rectified in the newest versions - Cachify does not treat its entire coroutine flow as the same scope, so it will be able to service more requests in parallel and join them to the running request. Highlander will cancel the old coroutine before entering a new scope, which allows for better transitioning from one job to the next. Do note that when Highlander cancels an old job to begin a new job, it will cancel the underlying CoroutineScope, which means that any code inside of a finally {} block will run when it is replacing jobs. Just something to keep in mind incase you use Highlander as a one-shot single job runner with a try-catch-finally pattern.

FridgeFriend gained a new transparent bottom nav bar which plays nice with the system insets, allowing content to draw underneath the bar. Scrolling is smoother and more animated. You can search any item ever entered into the app, and you can filter out data by things like when you bought it or how soon it expires. You can't install it yet though - as I have still not gotten around to putting it on the store. The curse of the ever lazy developer I suppose. I am working on an Android R compatible way to bring back the geofencing capability without requiring additional permission, though it may not make it into a first release.

One of these days, hopefully soon, I will decide to cut a release for the other apps whether FridgeFriend is ready or not. Hopefully by the time we next meet, they will be updated.

Stay tuned.

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Saturday, June 13, 2020

Branch Names

Many of the pyamsoft repositories have changed their default branch name from "master" to "main".

The master branch comes from the master/slave terminology. With our changing society and these changing times, we must distance ourselves from our old and outdated ways of thinking. pyamsoft applications are developed in the open for all to see, use, and be a part of. We encourage contribution from all peoples of all colors and walks of life.

All Android related projects have moved from "master" to "main". All of the AUR published packages have moved from "master" to "main". The "bubblewrap-wrap" script and the Franz extra service repositories have also moved from "master" to "main". One exception is the "android-project-versions" repository, which cannot be cleanly moved from "master" to "main", as production code relies on this branch existing to check for application updates.

Code related work continues to happen as well, with Highlander and Cachify receiving new releases to fix a recently introduced memory leak. More to come in the following weeks.

Stay tuned.

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Saturday, June 6, 2020

How to Stink at Coroutines

The main sell of Kotlin Coroutines is that they enforce a simple structured concurrency. Have a concurrent job that launches many concurrent jobs in parallel and waits for them all to finish before continuing? Coroutines make it easy - until they don't because you are misunderstanding how coroutines work and are wondering why your coroutines continue to run.

Let's observe the following code:

class MyViewModel : ViewModel() {

  fun mainTask(): Job {
    return viewModelScope.launch {
      doSubTaskAndCancelWhenMainCancels()
    }
  }

  fun doSubTaskAndCancelWhenMainCancels() {
    viewModelScope.launch {
      doWork()
    }
  }

}

val job = myViewModelInstance.mainTask()
...
job.cancel()

The mainTask uses the viewModelScope to launch a job, and that main job launches another job as a subtask. It may launch many subtasks, though for this example we just use one. Now - normally, the scope would be cancelled entirely in the ViewModel onCleared(), but let's say that this view model is Activity scoped but currently being used in a Fragment - the onCleared will not be called, so you must cancel this task manually when the fragment is destroyed to properly stop it.

Do you see what is wrong here? Because up until about 15 minutes ago I didn't.

The mainTask launches a job. The subtask launches a job. Though it appears that the subtask is scoped to the mainTask scope due to how it is launched, because we call the subtask via its own viewModelScope.launch {} call, it actually is completely independent of the mainTask. Thus, even when we cancel the mainTask, the subtask will continue to run until it is completed. This can create an issue, when for example the sub tasks are listening to event busses which expect to stop listening once the fragment is destroyed. Our concurrency invocations are not structed here - they are simply invoked in a specific order. To make them truly structured and have this code perform the way one would expect, you must change the subtask function.

class MyViewModel : ViewModel() {

  fun mainTask(): Job {
    return viewModelScope.launch {
      doSubTaskAndCancelWhenMainCancels()
    }
  }

  fun CoroutineScope.doSubTaskAndCancelWhenMainCancels() {
    this.launch {
      doWork()
    }
  }

}

val job = myViewModelInstance.mainTask()
...
job.cancel()

By making the subtask an extension function of a CoroutineScope, this enforces a structured concurrency. In fact, this is one of the first things the Kotlin Coroutine page tells you about how coroutines work - its a beginner mistake really - but one that may at first glance seem non obvious and so bears repeating. The only way to enforce the concurrency of subtasks is to launch those coroutine subtasks using the parent's CoroutineScope, and the easiest way to avoid shooting yourself in the foot is to make these subtasks extensions of the CoroutineScope so that you will never be able to call them in the wrong context.

On a related note - PYDroid 21.0.5 is out and fixes the bugs introduced by 21.0.4 which was introduced to fix the bugs in 21.0.3. Please use 21.0.5 as its API surface is the same as 21.0.3 but has some very critical bugs fixed.

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================

Tuesday, June 2, 2020

How PYDroid Arch Seperates Concerns

The seperation of concerns is a useful thing for a programmer to use while working. Splitting up code into logical groupings which only have one kind of job can help a programmer isolate both their mental model of what they are working on, as well as the potential sources of bugs when the inevitable report comes rolling in. I want to talk about how and why PYDroid Arch breaks its MVI based pattern up into 3 arbitrary groupings, and how these groupings help to seperate the various concerns of the Android programmer.

I always approach Android application development with the mindset that what I am building is 3 seperate parts of a program, which come together in a hopefully cohesive way to form a nice Android application. In what I believe to be the order of importance, these parts are as follows

1. How the program performs as a behaving citizen of the device.
2. How the program produces value for the user via the data it presents.
3. How the program visually looks and "feels" as it provides value.

To start, here is what my mental model looks like for applications in PYDroid's MVI styled pattern:

[Controller] <==> [ViewModel] <==> [View]

Let's start with the Controller first, since the Controller is the component which is most responsible for adhering properly to the first part of application building.

Here is the standard PYDroid Arch entry point for an Activity:

override fun onCreate(savedInstanceState: Bundle?) {
  stateSaver = createComponent(
    savedInstanceState,
    lifecycleOwner, 
    viewModel, 
    view1,
    view2
  ) { 
      handleControllerEvent(it)
  }
}

override fun onSaveInstanceState(outState: Bundle) {
  stateSaver?.saveState(outState)
}

In PYDroid terms, this layer is the Controller. The Controller is the biggest beast on the Android platform, as it actually has two different jobs (which breaks SOLID sadly) - that is to both handle the platform lifecycle events, as well as decide WHAT is on screen WHERE. This means all the Android specific things, like Activity callbacks, permission requests, outside application intents, service binding is all handled at the Controller layer. It also saves application state - which is critically important! The Controller has a two way relationship with the ViewModel. It receives Controller events from the view model - things like show a dialog or change screens, push and pop fragments, and so on - and it can also call methods on the ViewModel itself - though you should try to do so sparingly. In general, try to only have the Controller talk back to the ViewModel in response to an Android lifecycle event, like a permission callback or a lifecycle callback.

Next is the ViewModel. This layer oversees WHICH data will be shown on screen. It interacts with the data model of the application, talking to databases or the network or device storage, and decides WHICH data should be used by the application. It consumes this data by either publishing a one-off event to the Controller layer which will respond appropriately, or by updating the state of the view. The ViewModel in PYDroid is very similar to the standard MVVM Android ViewModel - just instead of using one livedata per variable and observing each of these variables seperately in the View layer, you have one giant stream which sends snapshots of the entire state model to the View layer. This makes it much more difficult for different parts of the View to be rendered out of sync, as you will always render complete snapshots of state. The ViewModel is the busiest object in the pattern, as it is the bridge between the Android system, your application's data and the visuals of your application. It has a two way relationship with both the Controller and the View. It can publish events to the Controller, and be called from the Controller. It also publishes data to the View by means of updating its held state, and receives events from the View in response to things like button clicks or text inputs. It is able to save state just like the controller too!

Finally the View, which is the layer which handles HOW the data will appear on screen. The View is updated all at once via a render function, which passes a complete state object from the ViewModel. Any views that are bound to the ViewModel via the createComponent call will all receive the same state object from the ViewModel at the same time. This is the layer where you control how things look on screen - the colors, the animations, the inputs and the buttons and whatnot. This layer is two way bound to the ViewModel, it can publish events to the ViewModel in response to user interactions like clicking or typing, and receives updates from the ViewModel in the form of rendering state objects.

By establishing and enforcing three distinct logical and mental seperations when coding, you can help isolate issues and create more robust applications.

If you need to integrate with a system behavior, like the back button press or a permission request, it should be handled in your Controller. If your application does not restore properly on process death or after rotation, check the Controller. If Views are not appearing at the correct location on screen, check the Controller. If you aren't correctly handling the result of an implicit Intent, check the Controller. Anything Android will generally be in the Controller, which makes it easy to isolate where a problem is occurring.

Anything to do with data, like databases or networking or shared preferences or file access, should be handled by the ViewModel. Things like Daos or http clients should be handled in the ViewModel via a use case or an interactor. If anything goes wrong with the nitty gritty number crunching, you can check the ViewModel to see whats going wrong.

And the actual look of your application is handled by the View. Your app is placing buttons at the right location on screen, and displays the right numbers, but your button is the wrong color? Check the View. The text isn't updating or an animation is not smooth? Check the View. Seperating the View out allows you to fully focus on the mindset of how your application looks and feels, without having to worry about whether the app is hitting the right network endpoint or what happens if the user puts the app in the background in the middle of this flow.

--------------

As some closing notes, since PYDroid is a heavily opinionated library, here are some tips about where I think of certain Android components in this 3 part structure.

Activities and Fragments are Controllers. Nothing else is treated as a Controller. While a Service would fit the description, it doesn't display any visual and therefore does not fit into the PYDroid architecture pattern.

RecyclerView adapters are sort of in the middle. They kind of act like controllers because of their requirement to construct view holders and bind view holders, but I treat them as a required bridge between the View layer RecylerView the adapter is held in, and the View layer ViewHolders that the adapter creates.

class MyAdapter constructor(
  private val onEventCallback: (ViewEvent, Int) -> Unit
) : ListAdapter(ITEM_CALLBACK) {

  override fun onCreateViewHolder(poarent: ViewGroup, viewType: Int) : MyViewHolder {
    return MyViewHolder.create(parent, onEventCallback)
  }

  override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    holder.bindState(getItem(position))
  }

}

class MyViewHolder constructor(
  owner: LifecycleOwner,
  onEventCallback: (ViewEvent, Int) -> Unit
) : RecyclerView.ViewHolder(rootView) {

  private val binder: ViewBinder<ItemState>

  override fun onCreate(savedInstanceState: Bundle?) {
    binder = bindViews(
      owner, 
      view1,
      view2
    ) { 
        onEventCallback(it, adapterPosition)
    }
  }

  fun bindState(state: ItemState) {
    binder.bind(state)
  }
}

This way, a ViewHolder can still be part of the View layer, the adapter is just delegating to specific holders at specific positions, and the view event is still handled by a ViewModel which is bound at the RecyclerView level.

Both the createComponent and bindViews will clean up after themselves by using the passed in LifecycleOwner to decide when they are out of scope, meaning you do not need any onDestroy or onUnbindViewHolder code to release things like callbacks.

Finally, there is some disagreement over whether things like Dialogs showing should be modeled in the View state or at the Controller level. Let me be clear - I believe that a Dialog being shown should ideally be part of the View state on other platforms like React, but on Android because Dialogs nowadays are implemented via Fragments, this makes them Controllers - and thus are modeled by one-off Controller events from the ViewModel, instead of state updates to the View.

========================
Follow pyamsoft around the Web for updates and announcements about the newest applications!
Like what I do?

Send me an email at: pyam.soft@gmail.com
Or find me online at: https://pyamsoft.blogspot.com

Follow my Facebook Page
Check out my code on GitHub
=========================