Friday, December 25, 2020

update-hosts version 3

A major version update has been released for update-hosts bringing it to version 3.

Version 3 is largely compatible with the older version 2, except that the script now requires you to select what is outputted from the tool, instead of outputting a hosts file by default. The tool can output hosts, dnsmasq, and ipset formatted files. Support for changing file names, output IP addresses, and adding additional sources on the fly is still present, but the unbreak list has been dropped as it was too far out of date to maintain accurately.

The script has been largely simplified and much of the output noise has been removed. Instead of logging each individual step, the script only outputs errors and success points. The average user doesn't care that the script is currently stripping line endings or parsing IP addresses, so that logging information has been removed.

Find the latest version here.

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

Thursday, December 24, 2020

git-ssh version 3

git-ssh has seen a major version refresh with version 3.0.0

Version 3 drops the python language as well as support for config file version 1 and instead moves to POSIX shell script with support for only config version 2.

For anyone using git-ssh 2 this change means very little as it is still config compatible, but for users using v1 you would honestly be better off just starting from scratch, it's quick I promise.

Some major behavior changes in this version though.

Gone are any of the --long-option flags, operation is strictly through commands now.

Use git-ssh create and pass it a name and an ssh keyfile path to create a config. Similarly use git-ssh delete and give it a name to remove configs. Use git-ssh list to show all your possible configs.

git-ssh in version 1 and 2 operated as a git subtool, meaning you woul call it along with other arguments to git itself. In version 3 it fills a role more like an outside manager, it's default and only mode of operation is now the equivalent of version 2 --ssh-alias mode

Calling git-ssh export with a config name will export a special environment variable that git will read when handling ssh commands. You can eval the result of this export command to set up your keyed environment. You can then call git as normal, so stuff like shell completion and git performance will remain as normal.

The choice was made to move away from python for performance reasons. Python took a couple hundred milliseconds to start up, which would slow down basically all git operations when git-ssh was used. Moving to shell script allows faster startup performance, and since the requirement to support old configs is gone, the tool is greatly simplified as well.

Find the new git-ssh here

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 22, 2020

Flashing Lights

Not gonna make the joke.

ZapTorch has received some updates today which simplify the code base and bring support for 2 new supported sequences!

Double tapping the Volume Down button will still turn the flashlight on, as it always has.

Double tapping the Volume Up button will turn the flashlight on and pulse it at a slow speed of about half a second. The light will flicker on and off every half-second or so, until you stop it by entering the sequence again. Cool!

Tapping Volume Down once and Volume Up once afterwards will put the light on in a strobe effect, flickering on and off quickly about once every 50 milliseconds or so. Seizure inducing!

This will be released in a future update.

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

Managing Multiple Heroku Accounts

Let's say you have multiple accounts on Heroku for whatever reason, maybe some personal, some business.

When using heroku-cli, it expects you to basically always be signed in to only one account, meaning you have to keep logging in and jumping back and forth between different accounts via heroku login. It's kind of annoying.

I took ten minutes today to write a shell script called herotate which manages your heroku-cli active login file, a hidden file called .netrc in your home directory.

The script will move this file into a configuration directory and create a symlink to it, so all your heroku stuff will work as it normally does. When you want to add a different account, simply herotate unlink, and sign into a new account. 

 To make things even faster, you can manage multiple configurations inside of herotate as long as they all have different file names and live in the same configuration directory, which the script will echo out to you if you run it. By default, your config is named main but you can rename it and the tool will automatically continue to populate the directory with any new .netrc files.

You can symlink a config of your choosing at any time using >herotate link <config> and list all of your configs using herotate list.

Hopefully this simple script helps you manage multiple heroku-cli logins just like it helped me!

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, December 19, 2020

More Flexible PYDroid

PYDroid 22.2.8 was recently released which allows you to create a component which is composed of multiple UiViews which all handle different subsets of a UiViewEvent instead of handling the same UiViewEvent. This allows you to separate UiViewEvent models up into things like just events for list loading, versus just events for error message displays, and the like.

Imagine the following:


sealed class MyViewEvent : UiViewEvent {

  object ButtonEvent : MyViewEvent()
  object ListEvent : MyViewEvent()

}

class MyViewModel : UiViewModel<MyState, MyViewEvent, MyControllerEvent> {

  handleViewEvent(event: MyViewEvent) {
    return when (event) {
      -> ListEvent -> listWork()
      -> ButtonEvent -> buttonWork()
    }
  }
}

class MyButton : BaseUiView<MyState, MyViewEvent, MyControllerEvent> {

  // Because it implements MyViewEvent, which was 
  // a requirement to be in a Component,
  // it is aware of, and can wrongly use MyViewEvent.ListEvent

}

class MyList : BaseUiView<MyState, MyViewEvent, MyControllerEvent> {

  // Because it implements MyViewEvent, which was 
  // a requirement to be in a Component,
  // it is aware of, and can wrongly use MyViewEvent.ButtonEvent

}


you can now separate the view concerns like so


sealed class MyViewEvent: UiViewEvent {

  sealed class ButtonEvents : MyViewEvent() {

    object Clicked : ButtonEvents()

  }
  
  sealed class ListEvents : MyViewEvent() {

    object Loaded: ListEvents()
  }

}

class MyViewModel : UiViewModel<MyState, MyViewEvent, MyControllerEvent> {

  handleViewEvent(event: MyViewEvent) {
    return when (event) {
      -> ListEvents.Loaded -> listWork()
      -> ButtonEvents.Clicked -> buttonWork()
    }
  }
}

class MyButton : BaseUiView<MyState, MyViewEvent.ButtonEvents, MyControllerEvent> {

  // Because it implements MyViewEvent.ButtonEvents, 
  // it can only use the Clicked action
  // and it can still be created into a component
  // with MyViewModel, so no code changes in the
  // createComponent callsite.

}

class MyList : BaseUiView<MyState, MyViewEvent.ListEvents, MyControllerEvent> {

  // Because it implements MyViewEvent.ListEvents,
  // it can only use the Loaded action
  // and it can still be created into a component
  // with MyViewModel, so no code changes in the
  // createComponent callsite.

}


More changes will be coming soon to PYDroid to help you better share your component views between different ViewModels and UiControllers.

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

Thursday, December 17, 2020

A Weekend with Animal Crossing

I was asked by my girlfriend to create a tool for her and her other Animal Crossing playing friends where she could keep track of a "wishlist" of items in the game that she wanted and her other friends would be able to check off items that they were planning on gifting to her, like an Elfster
for Animal Crossing.

Using simple Firebase tools and some React I built a simple frontend for the ACNH Api which I could use to display all of the items in the game. I used Firebase's realtime database to power a collaborative editing experience which handled the requesting and gifting flows of the items. It uses Firebase Auth to handle a email link based sign in, and uses Firebase hosting to power the web page. Neat!

It's called Celeste, named after the cute friend who comes to visit islands on a starry night. Since you wish upon a star, and this is a wishlist for you and your friends, I found it appropriate to name it after Celeste.

Since this was just a simple weekend project I won't be continuing it much further for the time being. My girlfriend found a fully running replacement site which I highly recommend you look at if something like Celeste has interested you. All in all, a fun weekend project and a nice look at how simple some of the hosting and platform services have become.

Find Celeste here

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, December 12, 2020

Some updates

PYDroid has been updated a bunch over the past couple of days to test the new in-app rating and update systems. I believe it finally to be stable, so the PYDroid version you should upgrade to from 22.0.4 is actually 22.2.7

Yeah, it took that many different build-test-deploy cycles until I finally ironed out all (most (some)) of the bugs.

FridgeFriend has been published with a new version bringing support for multiple Groups of Items, and all other apps are going through the internal and public beta channels. They will be published most likely later in the week.

I strongly recommend you update to these latest versions as they contain a bunch of fixes and improvements and better prepare you for any future updates as well.

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 8, 2020

PYDroid Autopsy

PYDroid Autopsy is a new library which helps during development by showing a crash report page on the device when an application exits due to an uncaught exception.

PYDroid Autopsy is a debug only library which requires 0 code changes to use as it self-installs via a debug only ContentProvider. It overrides the UncaughtExceptionHandler, and does not call through to any other handlers, so be warned if you are using other tools during development that Autopsy is currently not compatible with other tools which override the handler.

PYDroid Autopsy shows a red crash screen as well as the stack trace whenever a crash occurs and the device is able to capture the crash before exiting. While this is not a 100% solution, it more often than not will help catch crashes in development. It will also fire the stacktrace to logcat for those connected to the Android debug tools.

Try it out today! Find it here.

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

Faster Apps Better Libraries

It

Has

Been

A

While

First, the nerd stuff

PYDroid

Remember in your past life, PYDroid took this sort of stance

"When PYDroid triggers your view on a render event, it does not take the care to avoid duplicate re-renders when no data has actually changed. We believe it is best to handle re-renders in the UiView layer itself."

Ya so, times have changed thanks to StateFlow and its amazing ability to only fire events when attributes of a state have changed. Yay!

The old onRender(state: S) has changed to an onRender(state: UiRender<S>). The UiRender<S> is an interface which can allows the calling view to subscribe to changes and either emit the full state object, or only emit when a particular attribute on a state changes.

interface UiRender<S> {

  fun render(scope: CoroutineScope, onRender: (S) -> Unit): Unit

  fun <T> distinctBy(distinct: (S) -> T): UiRender<T>

}

This can improve performance dramatically in apps because now only certain relevant parts need to render when their relevant state piece changes. You still have all of the strengths of knowing that your state object is a single source of truth, but also gain the performance benefits of only drawing when you need to.

The distinctBy() function is useful for cases like, you want to show or hide a view based on a loading boolean, but the boolean does not control what data is present in the view. Before you would have:

fun onRender(state: S) {

  handleComplexChange(state)

  handleLoadingChanged(state.isLoading)

  handleDataChanged(state.data)

}

and both of these methods would get called even when just the loading state changed, or just the data state changed. Now you can be more specific:

fun onRender(state: UiRender<S>) {

  state.render { full -> handleComplexChange(full)

  state.distinctBy { it.isLoading }.render { loading -> handleLoadingChanged(loading) }

  state.distinctBy { it.data }.render { data -> handleDataChanged(data) }

}

For views that are more complex or derive their state from multiple attributes, the old expensive way can still be had by simply calling state.render() without any distinct filtering.

Migration should be made less painful by the fact that existing onRender(state: S) functions will continue to work, but are deprecated. Note that if you use your own custom class extending UiView instead of BaseUiView or PrefUiView, you will have to make a small API change to respect the new API. To keep existing behavior, call a plain render similar to the example above's handleComplexChange function call.

For UiViewModels, the state handling has been separated out to make the ViewModel less complex. UiStateViewModel<S> is a ViewModel which only handles accessing and mutating view state, it does not have bind hooks or clear hooks, and does not publish events to a controller. UiStateViewModel is powered by a UiStateModel<S> which is the class that actually handles a view state with accessors and mutators. This can allow you to use the PYDroid state model without needing to extend the full-fat UiViewModel, or even re-use a UiViewState modeled via a UiStateModel but have it interact with completely different UiViewEvents and UiControllerEvents in multiple UiViewModels.

The UiStateModel withState() function has been deprecated. To get the current state from within a state model, simply access it via the state variable. The setState function has also received an extra optional parameter, called andThen: (S) -> Unit, which is called only if the setState call results in a state change. In a future major version these deprecated functions will be removed.

PYDroid has also removed its custom update checking code and review prompting code in favor of Google's Play Store solutions for in-app updates and in-app reviews. This is done to make it easier to maintain these features, as before we were using a home-grown solution and Google's is much more battle tested, despite it resulting in a reliance on the Play Store. But, pyamsoft applications have only ever been published on the Play Store, so I do not see this lock in as a huge issue.

All pyamsoft applications have received support for the full-bleed window style when using gesture navigation. Gone are the days of a colored sliver of navbar where the gesture bar sits on the bottom of the device. Now the gesture bar will be transparent and will give a more modern Android look and feel.

FridgeFriend has gained support for multiple Groups of items instead of just one large Group of items. The multiple groups can be broken up into anything you find logical, like locations, or different fridges, or even by meal or food group. There is no strict system around what you can, and cannot do.

With recent Google restrictions to accessing location, FridgeFriend has made some changes about when it suggests you stores that are nearby. We already did not use Geofencing because of these restrictions, and do not request BACKGROUND_LOCATION, but Google has decreed that even a foreground permission level request, but made by something like WorkManager counts as background work. As such, you will only be able to receive nearby notifications from now on when you open the application.

Plenty more is soon to come and I hope to have a release out soon, but for now enjoy the holidays and stay safe.

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, September 26, 2020

On managing expectation, burnout, and monotony

Hey there, it's been a hot minute.

Couple of updates, nothing major in a sense, something huge in another. Let's get the elephant out of the room and back to the zoo - I'm tired.

Moment is dead. I've realized that my ambition to get it working was rather silly - given that plenty of other open source stock ticker applications exist and I could get the volume detection feature working much faster by just contributing to an existing project rather than rolling my own. Moment was a spark of inspiration that was edged mostly by not-invented-here syndrome. It was unrealistic to expect that I would be able to understand an industry and create an application tailored to a specific industry problem as an outsider in just a couple of months. The project is dead but the idea lives on in my brain, and may one day be contributed to another project. Here's the app that I currently use - maybe it will accept a contribution from a tired man one day.

Application updates are put on the shelf for about a week or two. While I did just release an update for FridgeFriend, I won't be doing much more work for the next few weeks unless something horrible blows up in the apps. I've noticed I'm getting into this mental rut of go to work (from home) and program. Get off work and program. Struggle with work problems that are a result of past coding decisions that now are affecting or limiting business opportunities and future business decisions. Struggle with personal project problems which are a result of past coding decisions that now affect or limit future coding enjoyment. Don't misunderstand, I still love doing this. I just don't love doing this all the damn time. I think I'll just need a small break - not from work since I need to pay the bills, but at least from a hobby which begins to feel more and more like work each day. Although that being said, I still am inspired now and again from the academic pursuits of programming, I just updated the PYDroid library with some hopefully less coupled ways to adopt the PYDroid MVI architecture, via adopting a lighter ViewModel into an existing architecture without needing to adopt the UiView and Component model. It's a longer technical talk which I'd love to indulge in one day - but not now.

To escape the bland monotony of my programming life, I've been attempting a couple of different activites. One being sleeping - the classic sign of depression. But aside from that, I've been playing music (and listening to music), I've taken a deeper interest in the puzzle game Tetris over at this lovely website, I've gotten back into smash, and I've been reading lightly to get myself into the hobby, and out of the mindset of just programming all the time.

Music has helped me to relax and engage a more creative side of my brain in a pursuit other than coding Though I would argue is programming of the most creative pursuits the human condition can take part in, the way of thinking becomes too result orientated in many instances and limits the creative expression that one needs to mentally and emotionally thrive. Both the consumption and creation of music engages the creative side that is less result orientated and lends itself better to simply creating for the sake of exploring the artistic side of ones process.

In a similar way, griding out games of Tetris like a giant fucking nerd also helps me engage a creative side of my brain that has been left out for a while. While I would argue there is a "best and most" efficient way to play Tetris, I would also argue that the human component of the game prevents the best and most efficient way from ever actually being used. People have comforts and preferences and style which they imbue into many facets of their life, including how they play a simple puzzle game. Do you stack on the side in a 9-0 or closer to a 6-3? Do you react to garbage with defensive downstacking or aggressive compounding? Do you go for a guaranteed T double now, or take a chance on setting up a Trinity? Do you even care at all? It is - after all - just a game. How one chooses to find enjoyment in a game is up to the player. Creative expression through a restrictive medium often brings out the best in us all.

Smash is like Tetris for me though I've had more experience with it. Simple to pick up, impossible to master. Let's not overestimate here, I'm awful at Melee. I love the game, I'm awful at it. But it is probably one of the most creative and rewarding video games I've ever experienced. If you practice you will be rewarded. If you can dream it, one day you will do it. Hands down the best fighting game I've ever failed at - one of my favorite games of all time. Eternally frustrating, but magically relaxing and exciting all at once. I highly recommend it to anyone who even has a passing interest in fighting games. You will love it. You will hate it. You will suck at it. Fair warning, you'll hate every other fighting game though. Not to the point that you'll never play them again, but just to the point that you'll know in your heart - whenever you play anything else, you could be playing Melee. Learning to play Melee will change your life - I don't know if it will be for the better or not, but you'll at least get really good at pushing the right buttons at the right time at the right speed. You'll play one of the greatest fighting games to grace the medium, and you may make some friends along the way. You'll always suck though. That one never changes.

I've always been a music guy, I've always been a gamer. I've - for a while now - been a programmer. It's no surprise to me that I would gravitate towards these activities when I felt like I needed a break, as I have done in the past and will continue to do.

Reading has been the most surprising escape for me. I don't read. I'm not a book lover, I'm not a literature guy. I read Harry Potter growing up. Stopped at the second book and just watched the movies. I owned the Lord of the Rings, and Redwall. I read the first page, the middle, and the last page of each (although I did read the Lord of the Rings appendix and extra bits). I just never cared for reading much. Sure, I read tech blogs, or video game reviews. But almost never books, except for school. Certainly never non-fiction books. Absolutely never any book that required me to think hard. How strange then - I've read and finished Man in the Holocene (again) last week, and I'm currently reading the Republic during my brief trips to the bathroom. I love it. It makes me think, really think, about things that will never show up in or affect my day to day life, and are yet so fascinating to consider and question. As a nice reprieve from the result oritentated rutt of programming, some of the questions posed in reading have no answer, and expect no answer. It is refreshing to not need a response. It is liberating to be able to think solely for the purpose of contemplation. It is refreshing to see an author pose a question, take a position, and leave the floor open for debate. It's a nice change of pace.

This has been long, and rambling, and I apologize. I've never been the strongest writer - perhaps because I was never the strongest reader. Hopefully after a couple of weeks of break and refreshment, I will return the strongest programmer.

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, August 11, 2020

Being in the Moment

So what's next for pyamsoft?

It's been a year since the last update - Android has rapidly evolved from the mobile OS it was when I first began into this worldwide phenomenon and it's userbase has grown exponentially. I no longer develop Android applications professionally, they have been relegated to purely a love and a hobby that I spend all of my free time in. I have thousands of users to please and, since I removed advertisements and all forms of monetary potential from all of my applications and open sourced all the code, I have zero dollars to show for literally thousands of hours of hard work. I've effectively lost money and time developing Android applications. Something has to change.

But change is slow and difficult, so instead here's another app in development.

Introducing Moment, or MMNT: a ticker tracker for the NASDAQ. It will periodically monitor the tickers on the NASDAQ for trading volume - and will send you a notification when the trading volume of a ticker exceeds 10x the average of the past 3 days. In this market of insider trading and rampant speculation, hopefully it can inform you about a burst in trading volume before the price change follows. It will also monitor specific tickers that you request and notify you daily about the total volume and price spreads from open to close. It'll have charts.

MMNT gets it name from the ticker of the same name which tracks Momentous Holdings, a penny stock trading at a volume of 4000 and going for about 20 cents. It has 2 employees. Its a small, penny traded stock that barely sees any activity and is worth barely any money per share. But, back in April of this year, its trading volume increased from a volume average of 150 to 120 thousand, which brought gains of more than 60% at the top. The massive volume increase preceeded the price increase by about a trading day, and the top by about a week. MMNT the app seeks to capture tickers like MMNT whenever its next great pop and drop happens.

It will hopefully be done in less than a year. We'll see.

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

Updates

Hey there - been a little while.

Updates were finally released today! Woohoo!

After a year of store silence, updates have been released for all pyamsoft Android applications - bringing them all up to speed with the latest architecture improvements and MVI state management and Android R support! The new release brings support for the Android App Bundle (AAB) format, so downloads should be even smaller than they were before (and they were only 2 or 3 MB before). This also paves the way for further support when the new Play Store APIs stabilize.

update-hosts received a small patch which removes sourceforge.net from the website blacklist, as the site appears to be no longer part of the badware risk on uBlock0.

FridgeFriend also has been pushed to the production track after a year of stringing you along! Its a minimum viable product at the moment, so it's not going to be smart or flashy or anything fancy like that - but it will be enough to validate the idea and also to kick me into gear about supporting it. Now that it is a live production app on the store (as long as it doesn't get banned) it will keep me honest about updating it. Expect to see it after review is completed, which should hopefully take only a couple of days.

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, August 4, 2020

Enforcing expected ordering in coroutines

A recent update to PYDroid brings it to 22.0.2 and brings with it two small but major changes.

First is the bump from the bundled Material dependency in pydroid-ui, moving from 1.1.0 to 1.2.0, hooray! See the changelog for the material-components library for details.

Second is a change to how the UiViewModel binds the view and controller. Before, the UiViewModel would launch multiple coroutines on the IO dispatcher and request to bind the controller event bus, the views event busses - then initialize the views - and then bind the state event bus and run the first render. Because these launches were all happening inside the same coroutine scope, they were queued in order but ran sometimes in parallel or even out of order - which goes against the expectation of a strict initialization ordering that the library guaranteed. Because of this lack of order, there were sometimes cases where the UiViewModel initialization could run before the controller event bus was bound, so any publish calls from inside the UiViewModel during initialization would get lost. Similar edge cases were possible in communications from the Views to the ViewModel.

The new version should address these edge cases, by making sure that the bus binding operations happen in order by launching the coroutine on the Main dispatcher, which is a single threaded unbound queue backed by the Main thread, and then immediately switching to an IO context to do their actual work. By queueing the work this way, this should guarantee the following flow

Controller bus is bound
View busses are bound
ViewModel is initialized, calls to publish() from a doOnInit block should reliably reach the Controller
Views are initialized in FIFO order, calls to publish() from a doOnInflate block should reliably reach the ViewModel
State bus is bound, any future calls to setState will emit a change to the view layer, but any setState calls before this will simply update the currently held state object without emitting any UI changes
The initial or most recent state of the UI is rendered.

Note that this flow has not changed, it was always intended to be this way - but hopefully with these changes this flow is even more strictly enforced.

As a side benefit to using the Main dispatcher as a queue, we can remove the unique stateContextDispatcher which was backed by a newSingleThreadedExecutor for each ViewModel, reducing the number of extra threads we are creating and destroying - since threads are expensive on Android this can be a nice benefit.

FridgeFriend finally got an icon today and the store page is beginning preparation for a release. Wow. It's been a while hasn't it.

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

Thursday, July 2, 2020

Pizza PYDroid

A new minor version of PYDroid is released which changes some internal behavior and some consumer level API changes that warrant a minor version bump.

Consumer API changes have to do with the doOnSaveState, doOnInflate, and doOnInit hooks on the UiView and UiViewModel classes. These hooks used to be inconsistent, doOnInit and doOnInflate would pass a UiBundleReader as an argument, while doOnSaveState would pass the current state as an argument (in the UiViewModel only) and the UiBundleWriter as a receiver. These have been made more consistent for consumers, though this does require a slight API change. The doOnSaveState hook has been changed, to pass both its UiBundleWriter and current state as arguments, making it more consistent with the doOnInit and doOnInflate hooks.

A more significant change is a library internal one, but one which may affect the behavior in applications. The way that a UiViewModel processes setState changes and withState callbacks has changed, and the hooks themselves have also changed slightly.

Hooks are not thread safe, but were not previously enforced in any way though attempting to multithread on hooks would cause errors. Hooks are now strict about the fact that they are UI related events, and so a hook must be called on the UI thread. For most consumers, nothing changes, but if you call a hook within a coroutine that uses a different dispatcher, you must switch context back to Dispatchers.Main before attempting the hook or the function will throw.

The setState and withState calls have some important behavioral changes. One thing that has not changed is that the functions are still asynchronous. However, the dispatcher used for both is a unique single threaded instance, so there will always be a FIFO ordering guaranteed for setState calls. A significant integration change is that withState calls will always yield to setState calls, so withState follows a FIFO ordering within the context of other withState calls, but will always run after all other setState calls. Because the setState and withState calls use their own dispatcher internally, they will not fill the IO or Default queue. While the setState and withState functions used to be backed by a MvRx inspired double queueing behavior, now they are implemented as a single queue, which will make single setState and withState calls simpler to reason, but can lead to confusion with nested calls. Nested ordering is not explicitly guaranteed, but you can generally expect the ordering to be by call nested level - all outer setState, followed by inner setState, followed by outer withState, and finally inner withState.

Another important change deals with the result publishing for setState changes. Previously, multiple setState calls would run and coalesce the result into a single call to render. While perhaps more efficient, this lead to issues where states could be missed as a result of the actual state change not being sent in render. Now results are published after each state change and rendered, and the view layer will provide back pressure to avoid excessive render calls, as well as a more expected order of render operations.

Finally the Activity extension function stableLayoutHideNavigation used to accidentally overwrite all other systemUiVisibility flags in favor of its LAYOUT_STABLE flags, but now it adds these flags onto the already existing systemUiVisibility flags.

The new PYDroid efforts can be seen in the latest developer build of FridgeFriend - which today gained a bunch of color theming polish and better iconography. Some crashes were also fixed and the application got an overall smoother performance thanks to rendering changes on the PYDroid end, as well as some UI optimizations and reduction of redrawing on opaque surfaces.

It is sadly, still not ready for prime time - but it's getting closer every day. I know, feels like a broken record, but I promise, once I get to a good stopping point to cut a initial release I can begin working on the new features I have in mind, which involve using your previously inputted purchase dates and expected expiration dates to derive a guess about when an item you purchased for a second time will expire. As clarification - this is not some fancy AI, since I don't know how to do that. This will be a very naive, on device math that will be simple to understand because I only have a simple grasp of concepts. It will not be invasive, and it will not be annoying. I also wish to get OCR support in for barcode scanning so that you do not have to interact with the software keyboard as much because typing on a touchscreen is hell. Geofencing will hopefully return after the use of a WorkManager powered foreground service loop, which will allow FridgeFriend to be battery efficient and also useful in regards to notifying you about nearby stores with items you need to purchase. I have plenty of ideas, I just need 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
=========================

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

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