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

Wednesday, October 23, 2019

Android Studio on Android

Kind of. Sort of. It's not perfect. Temper your expectations - and continue.

Let's set the stage - I have a Samsung Galaxy S10. Its a powerful device - 8GB of ram, 8 cores around 2GHz or so, not too bad for a pocket computer. It should be able to do things that a pocket computer would be expected to do - like run computer applications in my pocket. I like to code, I like to travel - I like to code while I travel and I didn't enjoy carrying around a seperate laptop everywhere. Here is my journey at trying to get Android Studio to run on Android.

What you'll need:

Patience.
UserLand
bVNC
Patience
A phone running Android
Basic Linux knowledge
Patience


What you'll be asked to download

Android Studio
The Android SDK
The Java JDK 8 built for ARM devices
Optionally the Kotlin compiler built for ARM devices


The rest of this assumes that you have some basic Linux knowledge and are insane enough to attempt running Android Studio off your Android phone.

First you should download, install, and setup Userland. I picked from a simple Ubuntu option and chose to connect over SSH. Once the Ubuntu install is finished, I launched into an session from the Userland app, and installed the following components from the package manager apt:

openjdk-8-jdk build-essential ubuntu-make tigervnc-standalone-server tigervnc-xorg-extension adb aapt

You will also need at least one android-platform from the package manager - on Ubuntu its platform 23. It's out of date, but that doesn't matter - we just need it so that Android Studio believes we have a valid SDK install. You'll also need to install Android Studio from the website - NOT the package manager. Head over to the Android Studio website and download Android Studio and the Android SDK for Linux tar files. You need to download the SDK seperately since the one bundled with Android Studio does not work on ARM.
From this point forward, I will refer to the SDK files as the Android SDK, and the stuff you got from the package manager as the repo SDK.

To steel your nerves for what you are about to get yourself into - largely things don't really work. You can expect many issues - know that if anything doesn't work your first instinct should be to try and replace whatever the file in the Android SDK is with the repo SDK.



You'll first need to set up TigerVNC so that you can connect to a graphical desktop session - you'll need to know a little bit about setting up VNC here, but here are the scripts I use every day to connect to the VNC server that I have set up on my phone.

The reason we want TigerVNC over the Userland default TightVNC is that Tiger supports the XRANDR extension - which we need to launch Android Studio. Use the absolute latest TigerVNC release for the best performance.

You can also install the DE or WM of your choice (I opted for XFCE4).

Once everything is downloaded, launch Android Studio and point it to the SDK you just downloaded. You can attempt the first time setup wizard - but it will most likely fail. That's expected - it is usually expecting to run things like adb or mksdcard which don't run on arm. Click on through as far as you can get and mark the wizard to not re-run. Close Android Studio.

Now for the first sync/index/file download. The first thing you'll need to do is STOP using the embedded Android Studio JDK - you'll have to go into both your Project Properties as well as the Default Project Properties and change the JDK target to the ARM JDK 8 you installed from the package manager, usually in /usr/lib/jvm. If you do not do this for both Project and Default settings, Gradle will not run. You will also want to manually copy the aapt, aapt2, and adb binaries you installed from the repo and replace the ones in your Android SDK location. For me, this meant copying /usr/bin/adb over ~/Android/sdk/platform-tools/adb and such. You'll also want to copy the repo-SDK Android platform that you installed (in my case platform-23) over to the correct directory in the Android SDK.

Once you have done this, open Android Studio and open the Settings popup - head over to the Android SDK settings under General - it should warn you about not finding any Android SDK setup because it failed earlier in the setup wizard. Click the SDK path to open the path picker and select your Android SDK location - this time it should treat the path as valid because you have a working adb, aapt, and 1 valid Android platform. It should succeed and now you can manage the Android SDK from Android Studio.

Download all the updates and get the latest platforms you need to support and all that fun stuff. One note - when installing a new Android platform there is a step during the process where Android Studio will attempt to stop the running ADB daemon. This step will fail if you have not put the repo-SDK adb file over the Android SDK one. NOTE that you must install the Build-Tools, SDK-Tools, and Platform-Tools file first on their own - copy over the old adb binary - and then install any new Android platform SDK updates. If you do not do things in this order the update will not go through.

Once you have done this, you should be able to attempt your first build - as long as what you are building is a library project. Normal Android applications will not build successfully because of the resource merge step which requires them to run aapt2 - aapt2 is not built for ARM in the Android SDK and so it will fail to run. Library projects should compile fine because they do not run aapt2, but normal Android applications do. Sadly this is a limitation of ARM at the moment - the only way to compile your normal Android applications is to change the plugin from 'com.android.application' to 'com.android.library', and in doing so you will be able to compile, but not actually build an APK on to your device. It's better than nothing I guess. You can see an example of what I mean here.

If you use kotlin (you should), you'll also need to manually build your own kotlinc so that it will run on ARM instead of using the version shipped with the Kotlin plugin. You can do this using ubuntu-make.

If you want to run platform tools like fastboot, you can install their packages from the package manager repository - but you won't be able to mix and match them with your current Android SDK. Even if you manage to install an ARM version of aapt2, when you go through a build in Android Studio it attempts to access a version of aapt2 from the ~/.gradle/caches directory which will not work - so its a dead end for now.

At the long end of your tiring journey - you have a phone which can adb to itself over VNC, a sort-of Linux distribution running on the same hardware that runs your Youtube app, and a way to - if nothing else - at least code and compile Android apps on the go. It works great in Samsung Dex mode, and can actually provide some decent results. It's not perfect, but its a fun little excercise in trying to get Linux onto everything that has a chip - and hopefully it will only get better in the future.

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

Friday, October 18, 2019

Library updates

PYDroid 20.5.0 is released, bringing the new hook style initializer and teardown handlers I mentioned in the last post! As mentioned before, the old function inheritance style is still present - and will continue to work until 21.0.0 at which point it will be unceremoniously slaughtered for the greater good.

Along with PYDroid, Cachify has seen a new release bringing it to version 0.0.3! The new release is backwards compatible with 0.0.2 syntax wise (but not binary compatible - sorry) and lays the groundwork for a full repository pattern. Cachify 0.0.3 now allows you to specify a list of CacheStorage objects, which will be iterated through one after the other when looking for cached data. The first object to present valid cached data will be returned, and only if no CacheStorage objects contain valid data will the upstream be hit. This change is transparent for consumers, and the simple syntax for creating a Cached instance which is backed by a memory cache storage is unchanged.

The one change though - previous releases of Cachify used public inline functions and called through to internal library implementations via the @PublishedApi annotation. With 0.0.3, this annotation has been removed, making the library not binary-compatible with 0.0.2. Unless you have a very strange setup, you should not notice this change at all.

And with the release of Cachify 0.0.3 also comes PYDroid 20.6.0! Wait what? Two releases of PYDroid in the same evening? Why yes! For those of you who do not know, PYDroid is this mishmash library which contains other libraries - like Coroutines, Cachify, and Highlander. The only difference between 20.5.0 and 20.6.0 is that 20.6.0 brings support for the new Cachify 0.0.3! I didn't want to remove the already released version and I figure its nice to know that not too many things are changing at once. For those who want to play it safe, you can use 20.5.0, but if you are adventurous and daring, try the new 20.6.0!

I'm hard at work on actual applications too - FridgeFriend is nearing completion and once it is done it will go out with the other pyamsoft applications - bringing support for Android 10 and all its shiny new things!

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

Wednesday, September 18, 2019

UI Architecture Hooks

I'm back with a fun little update to the PYDroid architecture library - this may get a bit technical, so hold on to your hats cause we're diving right into it.

PYDroid 20.4.0 just release with support for the new architecture framework that I've been building out - but I'm already hard at work on a fancy new version 20.5.0 which will include support for hooks to make it easier to extend and implement your code into the UiView based arch framework.

Here's a sneak peak of what's to come!

Before, when implementing your own extension of the UiView class, your inflate and teardown logic would look like this:


class MyUiView : UiView<*,*>() {

  override fun onInflate(view: View, savedInstanceState: Bundle?) {
    foo()
    bar()
  }

  override fun onTeardown() {
    fii()
    baz()
  }
}


Generally speaking, this was not too bad in the case where MyUiView was a final class, but what about when your class was open and would be extended further by another UiView? Then we ran into issues. If you wanted to run a third function on inflate and teardown such as fizz() and buzz(), you would need to either mark onInflate and onTeardown as final, and build something like your own onPostInflate(view: View, savedInstanceState: Bundle?) function to make sure that any classes which extend MyUiView don't forget to call through its onInflate, or you would override the inflate function in child classes and pray that you remember to call through to super. Both of these were problematic, as they were either error prone, or just plain annoying.

Enter hooks.

Now instead of overriding onInflate and onTeardown, you can do something like the following:

class MyUiView : UiView<*,*>() {

  init {
    doOnInflate {
      foo()
      bar()
    }
    doOnInflate {
      fizz()
    }

    doOnTeardown {
      fii()
      baz()
    }
    doOnTeardown {
      buzz()
    }
  }

}


Well that looks interesting, but what does it all do? The doOnInflate(onInflate: (savedInstanceState: Bundle?) -> Unit) hook replaces onInflate and the doOnTeardown(onTeardown: () -> Unit)) hook replaces onTeardown. The hooks execute their lambda functions at the same points in the lifecycle as the old brittle methods did, and they are guaranteed to run in set orders. This way you can compose together various bits of functionality without having to worry about the execution order or how to extend from a base class.
The doOnInflate hook will run its events in FIFO order, meaning the first function called in a doOnInflate hook will run before all the others. Conversely, the doOnTeardown hook will run in LIFO order, meaning the events run in reverse - the last event entered will be the first to run on teardown. A similar style hook for onSaveState(outState: Bundle) exists called doOnSaveState(onSaveState: (outState: Bundle) -> Unit) which does not make any guarantees on the execution order of its events.

Very similar hooks exist for UiViewModel as well to replace the brittle onInit() and onTeardown methods.

These hooks will eventually completely replace their old method counterparts, but at least in 20.5.0 the old methods will only be soft deprecated. They will still continue to work as normal, making 20.5.0 completely backwards compatible with 20.4.0!

Hop over to GitHub to see the source!


========================
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, August 31, 2019

Simplifying Architecture with Coroutines

It's been a while since we last spoke, and I've been hard at work.

Building further on my fun little reactive MVI architecture library - I've made some changes that I think you're going to be happy with. But first, some updates about apps.


My work in progress is almost done - I hope. At some point it will reach a clear enough level of function for a first release. The new app will be a utility that should help you manage the things in your fridge. I call it FridgeFriend - imaginative I know.

FridgeFriend is an open source application which serves as a psuedo shopping list manager, a sort-of fridge inventory management tool, and a kind-of reminder tool all in one. Before going shopping, you can tell FridgeFriend which items you plan to buy, including how many of each item - 3 boxes of strawberries, 2 bushels of kale, 5 apples - and it will remember your shopping list. Then, as you shop around at the store you can check each of these items off your list and it will leave your "needed items" shopping list and enter your "owned items" refrigerator list. When you attempt to add an item to your shopping list that you already have in the fridge, FridgeFriend will let you know. If you choose to tell FridgeFriend when your item will expire it will keep note of that too - and smartly remind you when your items are getting close to, or have expired. And don't worry about getting the date exact. Each time you get the same item, FridgeFriend will keep track of the previous expiration dates and will be able to suggest for you the correct expiration period based on all of your previous purchases. It may even be able to tell you cases where you consistently consume an item before its expiration date and remind you to purchase some extra.

FridgeFriend can also notify you when you have items on your shopping list and are within close distance to one of your local grocery stores or super markets. Since FridgeFriend respects your privacy, it will only inform you of the stores that you ask it to keep track of. Once you get close to a store, FridgeFriend can inform you about the items still on your shopping list - and may be able to suggest to you the stores which you most frequently purchase each item from.

All of this brainpower is local on your device, so there is no Internet and no tracking or analytics of any kind. FridgeFriend is entirely as smart as you let it be. Soon I hope it will be able to scan barcodes to enter shopping items for you, as well as consult an online food database about common shelf life for various items.

Of course, FridgeFriend is not yet done. Suggesting shopping items based on your history still needs to be built. Averaging the previous expiration dates still needs to be built. Frequent shopping location suggestion still needs to be built. As you can see, its still a little while away. But work is happening every day on improving FridgeFriend and making this dream a reality.

Unfortunately, life can get in the way. This is, after all, a passion project for me and therefore I can only allocate to it the time that I believe is not better served doing something else - and many times there is something more important that needs doing. So please, be patient with me and eventually I'll have a nice sharp new tool for you all to use.


You may also notice, that a Privacy Policy and Terms and Conditions have been added to all applications. This is strictly to stay in compliance with the Google Play policy guidelines and does not mean that my applications are taking any kind of radical shift in direction. They will always be open source, and will never track you or collect or sell your information. There will never be advertisements or incentives for donation, and all pyamsoft applications will be built for the users, and with the users in mind.


FridgeFriend, and all of the pyamsoft Android applications, will make use of my new MVI library. There have been some fundamental changes to how the library works - mainly that it has dropped the requirement of using RxJava to handle view and controller events in the UiViewModel classes. All events are now handled via built-in Kotlin coroutines, which are lighter on memory, faster on the device, and easier to read and write. The entire PYDroid library has moved to coroutines, which drops the requirement for RxJava across all pyamsoft applications. Take a look at the improvements to the ViewModel and state handling here!

I will most likely release updates to all applications once FridgeFriend goes live on the store - please be patient while I sort everything out. I hope you are as excited as I am to have a new tool to use 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, May 14, 2019

Who Owns State on Android

This is sort of a follow up to my last post.

The idea of state in an Android application is a confusing one. What is state? Why is it so important, and how do we attempt to get it "right"?

state in an Android application is the idea behind "who dictates what is currently being shown and being done on my device at this very moment". For example, for an app which opens a screen that changes colors between red, blue, and green, the "state" of that application could be which color is currently being displayed on screen.

Android has traditionally been very very bad at state management - I would argue it makes Android the most difficult platform to develop on simply because the job of state management is left up to the developer to manually implement in almost every single use case. Contrast this with something like web development, where HTML and scripts are cached by default unless you opt out, and strict frameworks like Angular's enforcing of DI principles or React's enforcing of one way MVI state help web developers keep their focus on the experience of using an application instead of worrying about how that tool works under the hood.

Don't get me wrong, I have many complaints with modern development and how it de-emphasizes knowing the importance of how the building blocks work - but I digress. Ultimately, at the end of the day we all want to build cool useful tools, not argue about the proper way to send a stream of bytes over the wire.

I personally believe one of the reasons why Android has traditionally been so poor at state management is because unlike these Web frameworks - or even iOS development which enforces a strict separation between Views and their ViewControllers, Android has historically never enforced any kind of recommendation or best practice - leading to many developers to learn with no idea of what even good practice may be. Android addressed the most general use case of state management, providing us with the simple onSaveInstanceState(Bundle?). Unfortunately, a general purpose tool that tries to be a little of something for everyone often ends up being nothing for anyone.

The reason why Scoop, and MvRx, Mosby, and Mobius need to exist is because of the failing of Android as a base platform to provide its developer community with a sane foundation to build on. The reason why I spent the last 2 months noodling on the idea of my own MVI framework (not saying it is any better than any of the other solutions - it was just more fun) was because these shortcomings of the platform had slowed me down for the last time. Even Google seems to acknowledge the mistake now that they are coming out with JetPack Compose which will one day be the future of Android UI.

UI is the one most difficult things in almost any application. Android applications are usually not very complex - they don't do much heavy lifting and generally just talk to servers. Their job is usually nothing more than to look pretty and feel cool when you click and tap and swipe around. Of course, not all applications are like this - pyamsoft applications are purely offline experiences using local databases or other methods of storage, so some heavy lifting is done in the applications, but nothing huge. For many cases though, the most rewarding payoff for time spent in development is to show a cool new page or animation to delight users and to provide a fun environment for working developers, or to build a cool new useful application that we use and enjoy day to day.

And Android is fun - its some of the most fun programming you'll ever have. It's so rewarding to finally get something to work and see it all come together before your very eyes. But some of that reward can be due - in part - to simply overcoming the large amount of useless ceremony that the platform puts in your way in order for you to get to your eureka moment.

Activity result callbacks, permission callbacks, fragment visibility callbacks, Activity window state changes, Window inset consumption, configuration change handling, service foreground state handling, notification channel management, receiver registration, sensor management, view related memory leaks - all of these are side effects of the platform that are left to the developer to deal with on a case by case basis. I love it - I love the freedom I get to build each application unique from the last, and I hate it when I just need to get things done.

And the crux of all these problems, the Achilles heel of UI development, is state and the ownership of state. On Android, who owns the state of a particular visual on screen?

Activities and Fragments are monstrous classes with many different potential roles. To save ourselves from going insane too quickly we always try to delegate much responsibility away from these classes as possible. There are only at max two jobs these classes should be required to do, handle system lifecycle events and handle navigation events. Everything else should be delegated away if possible. Do yourself a favor and save yourself in the future - your Android system classes like Activity, and Fragment - and to a slight degree Service - should be the ViewController, not the view. The only state these controllers would hold, is "which views are displaying on screen." This is complimented by things like the FragmentManager, which are only accessible from these classes, that handles its own internal state of "which fragment is displaying on screen." You should avoid View related state entirely if possible in your Activity and Fragment classes. UI state and the ownership of what is displayed on screen should be handled by the UiViewModel instead.

The View is a two part system - the UiViewModel and the UiView itself. The UiView should generally be a stateless class that knows how to color and shape and position itself to look pretty. The UiViewModel is a simple data class which holds a representation of the current look of the View in code. So for example, a UiViewModel like

data class MyUiViewModel(val isRed: Boolean, val displayName: String)


Would convey a model that controls the displayName rendered inside of it's UiView's TextView and whether or not the background color is red

By keeping the UiView stateless, any scenario can be rendered on demand. "Hello World" with a red background, "Hello Foo" without a red background, a blank name and a red background, and so on.

As we've seen in frameworks like React, UiViews are only updated via a single render(T) method which is given a snapshot of UiViewModel state as its only T parameter (some implementations also pass the previous state T as an additional parameter). This way, we can be guaranteed that any UI changes are all happening at the same time, and that the UI will always consistently display itself as a function of state.

By letting UiViewModels be the single source of truth and own the UiView state entirely, we can avoid Android lifecycle related bugs and achieve more consistent and predictable UIs. Of course, this can come at the cost of more typing - but the trade off of less cognitive load during debugging is its own reward.

Its not perfect - and it never will be. Modeling state in an asynchronous system like Android means bugs - and bugs are eternal. Problems still exist outside of the realm of state though, like what is the scope of each model of state? What, if anything, in Android owns the global "top down" view of application scope - and how can we synchronize the various bits of UI state between multiple views?

Perhaps another day.

For more - certainly better - reading on the architecture around Android and state, see this and this to understand why I am harping on state so much.

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

Friday, May 10, 2019

UI Architecture Design Patterns

At Google I/O 19, the Android Jetpack team announced Compose, which basically brings a React style UI toolkit to Android. Awesome.

For those who haven't worked in Javascript land before, I highly recommend you take a look at React and read about its philosophy behind the handling of state and data.

tldr; By enforcing a strict 1-1 relationship between a given component's state and the actual visuals drawn, you can be sure that data is always synchronized -- what ever business logic manipulates is what the screen will draw.

However, as with all nice things, Compose is not ready yet for prime time. In fact, it's not even ready for developer time - as it currently is in a pre-alpha stage. Google does recommend though that you prepare your applications for a world where a one way composable view state becomes the norm - and we don't have to wait at all to start doing that!

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

Recently I was inspired by a talk given by Netflix engineers at a Droidcon event back in 2018 where they outlined how Netflix approached UI building via independent components which they composed into a screen. I adapted their architecture into the following couple of basic classes.

UiView.kt

@CheckResult
@IdRes
fun id(): Int

fun inflate(savedInstanceState: Bundle?)

fun saveState(outState: Bundle)

fun teardown()



UiViewModel.kt

fun bind(onRender: (state: T, oldState: T?) -> Unit)

fun unbind()



UiComponent.kt

@CheckResult
@IdRes
fun id(): Int

fun bind(
  owner: LifecycleOwner,
  savedInstanceState: Bundle?
  callback: C
)

fun saveState(outState: Bundle)





The UiComponent class would take any number of related UiViews and UiViewModels and would result in self-contained components that could be dropped into Android Views, Activities, or Fragments. Not bad - but there were many shortcomings with my implementation.

The application state was a two way flow, which would prove to be difficult to manage. Because a UiComponent was meant to be self contained, it was both a View and a Controller. My attempt at separating logic out of Activities and Fragments did not actually fix anything - it just changed the name of the problem to UiComponent. As a result, the relationship between UiView and UiViewModel was only loosely defined - sometimes the view would listen to the model and change how it displayed content on screen, sometimes the model would listen to the view and fire off navigation events using some rather hacky logic.

There also was the issue of creating the UiComponent class for each of these UI widget groupings - it was an extra file that really did not do much on its own except abstract other classes needlessly. It had to be better.

I went back to the drawing board and reasoned about how I could simplify this basic 3 class architecture I had built.

My first win was getting rid of the UiComponent class all together. It was basically a useless extra - it served as a Controller for the UiViews passed to it - but I had already established that in my Android architecture the Activity or Fragment was the controller. There was no need to have a Controller wrap another Controller, so UiComponent was removed.

UiView was a little bit trickier. Because Android's current view system is a far cry from reactive composable state, I had to make do with the current UI toolkit - which means I was required to have an inflate(Bundle?) method as that was the step where Android would inflate layout resources and find views and whatnot. Similarly, I needed to keep a teardown() method to release resources, as well as a saveState(Bundle) method for process death persistence. The id() method helped with laying out my UI widgets inside of ConstraintLayout containers, so it too stayed. Looks like I wasn't able to simplify UiView any amount - but that doesn't mean I was not able to improve it.

The main problem with UiView is that there was no established contract for when a UiView updates. Taking a page from the React toolkit, I added a render(T, T?) method which I would adopt as the only place to make dynamic UI updates in a UiView class.

Next I tackled the UiViewModel. My main issue with this class was that it required an arbitrary lifecycle of bind() and unbind() in order to make it possible to use at all. After working at it for a while, I managed to reduce UiViewModel to just one function, render() which will treat its lifecycle as the lifecycle of the RxJava stream it associates state events with. Meaning, once the RxJava stream opens, the ViewModel is "bound" and once the stream is disposed, the ViewModel is "unbound".

Instead of manually passing a callback to and from ViewModels and UiViews, I used an RxJava bus - one for the UiView as a stream of interaction events, and one for the ViewModel as a stream of presentation events. This way, a View would only emit events to its ViewModel, and the ViewModel would only emit events to the Controller. A uni-directional data flow at last.

Finally, to wrap up things and make my new spicy code play nicely with Android, I built a small stateless function which takes any number of UiViews and a ViewModel which controls them all and performs some boiler plate to set everything up. This createComponent function will inflate all Views, open the ViewModel state stream and UiView event stream, and register to a LifecycleObserver, which will dispose the stream and teardown the views when the lifecycle is destroyed. Much nicer.

I'm not the best speaker or the best writer - and can't really convey these ideas easily over a post. You may just be better off looking at the source code itself to gain a better understanding of what I'm trying to do. The buggy code for the new and hopefully improved UiViewModel can be found here. Here is the new UiView code, and my simple component wrapper.

I'll be updating all apps to follow this new architecture and eagerly await the day that Jetpack Compose releases. This new architecture should lead to fewer bugs for all the existing pyamsoft Android applications, as well as speed up the development process of my new application that is still a work in progress... but that's for another day.

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

See my code on GitHub  
=========================

Tuesday, April 9, 2019

What's Up

Updates that's what.

Updates rolling out today/tomorrow for Home Button, ZapTorch, Pasterino, and WordWiz.

PYDroid has been updated to 19.2.X which is the first series to bring a new -arch module which provides an MVI design pattern for easy application building - it is powering all pyamsoft apps!

I'm working on something new at the moment - under wraps for now (unless you're able to read Git commits) since I need to make sure I have all the features rounded out as far as being feasible or not before I announce anything.

This release also marks the deprecation and unpublishing of PadLock off of the store. Unfortunately, like Power Manager before it - it is no longer welcome in a constantly changing Android environment. F for PadLock. F for Power Manager.

Long live pyamsoft.

More when I can, 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
Follow my Google+ Page
=========================

Wednesday, March 13, 2019

Long Time No Post

I feel like I've written this one before.

Time goes by and life catches up to us all, and I am no exception. I've been away from posting for a while, but have not been away from development. In fact, I've been working for the past couple of months re-architecting all of the pyamsoft applications to use a fancy new View-Ui-Component architecture that was introduced by Netflix at a Droidcon in 2018. It's been a wild ride and has opened up new ways for me to think and design applications.

Speaking of the applications, I've spent the last few months working on PadLock, and was hoping to release an update once I was satisfied with the codebase. However, with the recent Android Q beta announced, I find myself halted in my tracks by the big G themselves.

Android Q will be fantastic I'm sure, but it brings a change that is fundamentally incompatible with how PadLock operates. This change in how Activities can be launched from the background is the death knoll for PadLock and possibly many other applications. While I avoided the SMS fiasco, no man is safe forever.

As such, I am ceasing PadLock development. Sadly, while I am sure I could use a system level OVERLAY_WINDOW to get around the current background requirements, I am sure a future day will come where that is deprecated too in favor of PiP mode, or some official overlay API. Neither would work for PadLock's case - as its job is to be a full screen locker and directly intrude on the usage cycle of a nefarious user. With Android's continued lock down on the ability for applications to do unique and complex things across the device, PadLock is no longer a welcome application in the new world ecosystem.

I will still release updates for the other applications, but please note that once you upgrade your device to the shiny new Android Q later this year PadLock will cease to work at all. It saddens me to admit this, but it was fun while it lasted. I hope many of you found use of the tool I created.

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
Follow my Google+ Page
=========================