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
=========================

Monday, May 11, 2020

FridgeFriend not dead

I promise I still work on it. I promise.

It's coming along still, a lot has happened in the past couple of months, obviously, and I was never quite able to cut a release like I would have liked to. Rest assured I still use it every day, its still being developed, its quite useful if I do say so myself, and it becomes more useful each day.

I've added the ability to sort the items you currently have by the date they were purchased, or alphabetically, by the earliest upcoming expiration date, or just by the order that you entered them by. I have added item searching to quickly filter through your potentially long list of items for just a few specific ones, the ability to quickly update the quantities of how many items you have in your fridge, as well as overall stats regarding the items you have and how many are fresh or close to spoiling.

My main priority at this point is to cut a first release, but future updates will expand on the possibilities of the function that FridgeFriend will have to offer. That is all I can disclose for now, but I hope you are as excited as I am.

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
=========================

PYDroid 21

PYDroid is versioned enough to legally drink.

This new release deprecates the BindingUiView interface and moves its ViewBinding related code into the BaseUiView. Long live ViewBinding!

The UiViewModel state is bused using the brand new StateFlow interface which allows it to be easier to maintain and coroutine and thread safe by default. This change is transparent.

This release also adds support for a page about other pyamsoft apps! If you do not have a network connection, or have not allowed the app to connect to the internet, clicking on the "More apps" button in the settings page will still perform the old behavior and route you to the pyamsoft page on the Play store, but as long as you are connected to the internet, it will now load a new page which provides you links to all pyamsoft apps as well as links to their store pages and source code!

This is the first major release in a long time and it will be followed by updates to the pyamsoft android applications. I know, its been a long long time coming - work and life and the coronavirus have all gotten in the way. FridgeFriend is not dead - neither are any of the pyamsoft android applications. I do want to release soon, but FridgeFriend still needs some general UI cleanup and feature implementation to truly be customer ready. Please be patient as always, you'll hear from me again soon.

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
=========================

Tuesday, March 3, 2020

Android 11

With Android 11 come some new permission changes - in particular - Google is no longer allowing any application to easily request Background Location permission access. If this permission process is anything like SMS was a while back - nobody will get background location permission.

FridgeFriend was reliant on this functionality to use geofencing at the user's request - and notify you if you have come within range of a marked supermarket and need to still purchase some items. Sadly with these new restrictions this is no longer possible.

While this nice feature is gone, as long as the application is open FridgeFriend can notify you about nearby stores and needed items. I am also working to include a feature to notify you in the evening about cleaning out any items from the fridge which have gone bad and polishing up your FridgeFriend list of items so that it is ready for the next day.

Release is - sadly - not yet. Life gets in the way but we keep working. One day - I promise.

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
=========================

Tuesday, January 28, 2020

One state, two state, previous state, new state

With the latest changes to PYDroid in 28, you'll probably notice that when renders occur, the render function is called only with a single State parameter. In fact, throughout the history of the PYDroid Arch library, there has never been an instance where the previous state was also passed through the render. PYDroid doesn't pass the previous state, PYDroid doesn't like the previous state.

Why not?

Ultimately, its a philosophical decision and not a technical one. Before we dive deeper, we should understand why the previousState is generally a wanted thing.

In many cases with MVI frameworks, the render function is responsible for drawing and updating the entire visual part of state. In many cases, the previousState and current state are used in this render function to avoid unneeded renders or re-renders. Sounds like a good thing, so why doesn't PYDroid like it?

I would argue that the point of MVI is to simply how to draw a screen - by giving the consumer a single parameter and saying "everything on screen must come from this state object". By including in the mix a previousState, the idea of rendering on to the screen is now a function where the output is a complex combination of two very specific inputs, instead of just a simple object. Adding previousState makes it just as tedious to track proper state as one would experience in a non MVI setup. It does have the nice benefit of avoiding un-needed drawing and rendering, but at what cost?

So then PYDroid doesn't use previousState, so therefore PYDroid can cause useless rerendering and is slow. Well - not quite. PYDroid debounces on states which are the same - if a state change ultimately results in the exact same state as the one before it, the render is never called. This is different from a framework like React, which will render each time setState is called as long as shouldComponentUpdate is true.

Then, how does PYDroid avoid the situation where a state change occurs, but only one component of the state has actually changed? For example, if your state holds an error Throwable and a loading boolean - how can you avoid re-rendering the view when only one of these changes but not the other? Well, PYDroid does nothing. It just sends the render call to the view layer.

But remember what our View layer is. We are not on the web, where the View is created and rendered as just a DOM - we are on Android. Android views largely handle these duplicate state issues on their own. Passing the same text string to a TextView will not cause a redraw. Passing a drawable to an ImageView will not reload it. Passing a new list to a RecyclerView adapter will be diffed via the submitList function, and only the individually changed ViewHolders will be re-rendered. Basically, PYDroid doesn't care - it delegates the responsibility of avoiding useless rendering to the View layer which does the rendering. In the event that you really need to manage previous states and avoid re-rendering, you can keep track of the specific components of state on your own.

Maybe in the future this may change if decide it is more trouble to not have previousState - but at least for the lifespan of 28, PYDroid will be a single state MVI framework.

========================
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
=========================

Wednesday, January 22, 2020

New droid, my droid, good droid, pydroid

PYDroid changes and incompatible APIs, name a more iconic duo.

The next version of PYDroid will be 20.8.0, which will be generally sane, but will require some API consumer changes to work.

The first big change is that the use of directly referencing Bundle objects in UiViewModels and UiViews is gone! Replacing it are two new abstractions, UiBundleReader and UiBundleWriter. I assume you can guess what they do. You can create a new reader or a writer from a Bundle using the respective Companion create method. They have a similar API to the normal bundle and should be simple to migrate to.

The perhaps larger change is an update to the rendering of UiViews. Gone is the notion of the UiSavedState class, as it was a class which held saved bundle data but was also stateful and sometimes returned data and sometimes did not depending on when the data was last consumed. This posed problems for nested render loops which relied on the same saved state. As a result, all render and onRender methods now only take a single parameter - state.

The init, inflate, teardown, and saveState functions on UiView which come from its interfaces are now final - please use hooks instead of overriding these if you extend from UiView directly. Any other consumer, like those from BaseUiView, PrefUiView, or just consumers of the IView interface should notice no changes.

As a result of these changes, some memory leaks have been plugged in the pyamsoft applications - I will try to get a release out soon. FridgeFriend is ever vigilent and still coming along.

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
=========================

Monday, January 13, 2020

Suspending SharedPreferences

In an effort to future proof pyamsoft applications for what I assume will be a future announcement of deprecating SharedPreferences, I have updated PYDroid to access all preferences as suspending functions. This will avoid activity on the main thread as SharedPreferences are initialized and should improve read performance.

Initialization hooks on UiViewModels are now stored and replayed from a Set instead of a List, so ordering of these hooks is not guaranteed. While still guaranteed for UiView instances, you should not rely on this ordering as a strict API guarantee. Anything that requires ordering should run in the same block of a given hook.

FridgeFriend has received more improvements on its quest to finally - finally - FINALLY be released... and its not quite there yet but I will release it one day. Since a new Half Life is coming out I suppose my pool of excuses is slowly drying up. After all this time, I'm a bit anxious about releasing what is effectively still an MVP of an idea - but hey that's what you get with hobby time projects.

I've got more to talk about but its a nitty gritty nerd topic - so maybe another time.

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
=========================

Monday, January 6, 2020

PYDroid Party

Happy new year, happy new PYDroid!

PYDroid 20.7.0 has been released over the holidays! This finishes the deprecation of the old override API for the PYDroid-Arch component, and brings the new hooks API as well as the ability to nest BaseUiView<S, V> objects within each other as long as the state and event types are the same!

This also brings a shiny new convenience API called the StateSaver

  private var stateSaver: StateSaver? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    ...
    stateSaver = createComponent(savedInstanceState, this, viewModel, view1, view2, view3) {
      ...
    }
  }

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


StateSaver helps to reduce the boilerplate of saving instance state on all your views and viewmodel by wrapping them all up into a single object and calling save state on everything all at once. By marking the object as nullable and creating it in the onCreate function, you can guarantee cases where onSaveInstanceState is called before construction (like in Fragments when you rely on onViewCreated) will never crash your application - as the StateSaver will only be available once the view is created.

Oh, and speaking of saving state, UiViewModel now has a doOnSaveState { state -> ... } hook which will let you hook directly into the Android state saving lifecycle. If you are correctly modelling state in your application using UiViewModel and UiViewStates, in many cases you can remove code out of your UiView save state hooks and into the view model hook. The UiViewModel init hooks also gained support for accessing the save state bundle in the doOnInit { savedInstanceState -> ... } hook.

BaseUiView<S,V> (one's which inflate and use an Android View or ViewGroup as the parent object) have gained support for nested UiViews. You can use the top level UiView layoutRoot as the container for nested BaseUiView objects - as long as the layoutRoot is a ViewGroup. This is a bit of an API hack - as we override the parent argument passed in with the constructor and use the containing UiView instead. In a major release like 21.X.X, this API will probably be re-worked to be less clunky - but I wanted to make nesting available without breaking existing UiView setups.

Each UiView and all of its nested UiViews will be subscribed to the same UiViewModel and will receive all events in parent -> child order. This includes inflate, teardown, and save state hooks. You can nest BaseUiView objects using the nest(vararg view: BaseUiView<S,V>) function from within a BaseUiView object - usually within the init block. Though you theoretically could dynamically nest views on the fly - it is generally recommended that you always nest views in the correct relations - and then dynamically show or hide the needed views on the fly. Save your sanity.

The PYDroid UI reference library gains some new function as well - all animators in the view animator utility package will now return the animator object instead of throwing it away. This will allow you to pause and cancel animations in progress if needed. The listener override API will now append your listener to the default animation listener instead of overwriting it entirely - this change should not be noticed for too many people - but be aware as it is a minor behavior change.

List level ViewHolder performance has been improved thanks to the new ViewBinder<S> API. The ViewBinder is a lightweight component-like API, which groups a lifecycle owner with an abitrary set of UiView<S> which all share the same UiViewState. Instead of responding to UiController events, the Binder will respond to UiView events - this is because the view holder itself is mainly a light weight display container. All events would generally be forwarded to the list component which will be a part of the controller. The ViewBinder has a single method, bind(state: S) which will fire a render on all of the held view components. This helps to simplify playing with the RecyclerView ViewHolder framework - layouts are inflated, click listeners are set, components are injected or constructed - and the the ViewBinder is created via the top level bindViews(vararg views UiView<S,V>) function inside of the onCreateViewHolder function. The ViewBinder is used in the onBindViewHolder function to bind a model mapped to UiState to the list item. Generally the lifecycle owner is the container lifecycle owner passed into to the onCreate for the view holder - this allows all items to be bound and unbound with the container state.

class ViewHolder(parentLifecycleOwner: LifecycleOwner, itemView: View) : RecyclerView.ViewHolder(itemView) {

  private val viewBinder: ViewBinder<MyListItemState>

  init {
    ...
    viewBinder = bindViews(parentLifecycleOwner, view1, view2, view3) {
      ...
    }
  }

  fun bind(state: MyListItemState) {
    viewBinder.bind(state)
  }
}


You should notice these performance changes in the Open Source Licenses list page in pyamsoft applications.

Finally, though not documented, UiView initialization is now broken up into init(savedInstanceState: Bundle?) and inflate(savedInstanceState: Bundle?) steps. This is used for the new view model APIs, and breaks up the roiot view inflate step and the later inflation hooks into two seperate processes. If you, for some reason, do not use the standard UiViewModel component API, you will need to update your UiView classes to handle this view. It can be a no-op.

All of these big API changes are in prep for FridgeFriend to finally - finally ... finally release ........ someday soon! The holidays were a super lazy time for me - and as a result - I ate and drank and generally did zero work. So, continue to be patient as I continue to work hard and at some point I will deem the app good enough for a first version and that will be that.

But until then,

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
=========================

Tuesday, December 10, 2019

The end of override and long live the hooks

PYDroid 20.6.10-SNAPSHOT is the current version being used in all the in-development pyamsoft applications - and it will be the final version to support the overrides for onInflate and onTeardown.

Going forward with PYDroid 20.7.0, these overrides will be gone and the hook methods will be the only remaining ones.

Not to worry - if you are not a developer this does not concern you - and if you are a developer - thanks! But the overrides have been deprecated since 20.5.0 and its time for them to go.

You know what its not time for? In-app purchases. Ever.

My payment profile on my merchant account appears to have been closed as a result of closing what I thought was only access to the Google Pay app. Oh well, live and learn. As a result I am unable to associate a payment profile and cannot create, manage, or view in-app purchases in the console ever again.

So any form of monetization to keep me able to continue working on these apps will have to come from somewhere else, oh well.

I did however manage to get a full branch on PYDroid up and running called billing which integrates the Google Play Billing Client library into the UI architecture - but its completely untested and will probably forever remain untested. You're welcome to branch off and try it, but I don't recommend it - unless you want to learn about how to make coroutines work with a strictly callback based library and how it fits into an existing MVI architecture. Actually - that is rather interesting - so maybe it would be worth a look at the PYDroid "billing" branch.

FridgeFriend is so so close. I know you've heard this before - in fact you've been hearing this for almost the entire year. Happy procrastination anniversary! It really is gettting closer every day - and once I can convince myself that it will never reach my own personal standards for what a "complete minimum viable product" is, it will be released. To ease your fears about it dying an early death - I've been using it in my own life every day since like May when it first reached a usable state, so far so good. Nothing has spoiled in the fridge since.

I'm debating to continue publishing here versus moving over to a more 2019 platform like Medium - just in time for 2019 to be over. If I do decide that change is coming, rest assured I will publish far in advance about how the move will happen.

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
=========================

Tuesday, October 29, 2019

More Library Updates

Cachify and Highlander have received small bugfix updates today. Cachify has bumped to 0.0.4 and Highlander to 0.0.3.

Cachify has changed to allow for more verbose logging when enabled regarding what steps the runner is taking at what point in time. The multi-backend setup is also now backed by a Mutex to guard against concurrent access of an Orchestrator over different coroutines. It also fixes a bug where - if a cache is spam called quickly enough, the runner will attach and await a coroutine which is already completed or cancelled - which would cause the Orchestrator to attach to a runner that returned a value successfully - but tossed it out into the void.

Now the runner will check that an active task is still currently alive, and only attach to it if that is the case. If a task is active - but cancelled or completed, the runner will consider the task no longer active and clear it out - and then it will wait for a new active task, or take over as the active task.

With these library updates, PYDroid 20.6.1 has been released which just pulls in these new library versions.

These small bugs were discovered as part of a testing run on FridgeFriend to make sure that it can correctly handle users spamming buttons, clicking around, and any such other nonsense. Every day I am getting closer and closer to preparing for a release - and then I get to battle the new Play store and its policies -_-

Thanks for your patience, this has been a passion project of mine for the past year or so now and I'm really excited to see it nearing completion!

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
=========================