Ok, I admit, this is a clickbait title, but you’re here, right? So let’s keep going. Thanks for joining me.
Here’s the TLDR …
I removed an AppBar that was in a ComposeView from my app because using an XML Toolbar is far simpler and required less code. Pragmatism 101. Read on for the full story of why and how …
Did I remove Compose from my Android application?
Yes, sort of …
I only removed the App Bar Compose component, the other Compose screens are still fully functional and I have zero plans on removing them. I have some fullscreen fragments that are 100% Compose and have no plans to remove them.
Why did I remove the Compose App Bar?
It’s important to state that this is a very nuanced and special circumstance as normally I would not remove Compose.
In other words, if there’s nothing wrong with the Compose implementation, why would I take it out?
I wouldn’t.
In fact, I like Compose. I learned React in 2017 and fell in love with its unidirectional data flow and its state management, etc. It was fun to write at the time. So when Compose came around I liked the idea and still do, for the most part, though it does have some warts, no doubt. This is Android after all, it’s not all puppy dogs and rainbows.
The reason why I removed the App Bar is because I’m the developer of Jumpstart Android and I needed to upgrade it to include support for Strada.
Strada works along with Turbo Android.
Real quick, what is Strada?
Think of Strada as web to mobile bridge that allows a web app, running in a web view to make calls into native Android code, and vice versa. It is the bridge between the web and native. More info on why the web is involved in a minute …
Ruby on Rails, Opinionated Software, Convention over Configuration
I’ve been using Ruby on Rails since version 2 in 2007. Ruby on Rails is an opinionated framework. This means that it guides you to do things into their way of doing things. If you attempt to fight the framework, things will get difficult because you’re having to jump through hoops and go around pre-defined implementations. It’s possible, but not advisable. It’s well known that Ruby on Rails is a very opinionated framework and for good reason – if you follow “the rails way” of doing things, well … things just get much easier and they “just work” most of the time with very little additional effort.
This is possible because of following a convention vs adhering to a configuration.
AKA: Convention over Configuration.
Ok, what does that mean?
Ahh, good question, I was also often baffled by this very vague quippy sentence.
But the thing is, the definition is right in the words.
If you follow a particular convention, you wont need to configure much. Things just work.
That means you put your controllers in a particular place, use specific controller action names, use specific model methods, put models in a particular place, use singular vs plural in the right place, set up your routes using resources and proper nesting, etc.
In other words, if you just follow the existing convention of a rails app, things just work naturally and effortlessly.
Juxtapose that with other frameworks that make you provide lengthy configuration setups and have a million options for every permutation of an implementation/etc. It’s a vastly different experience in rails, and often, many developers rage against it when they first use it because they have their own way of doing things, so they often end up fighting the framework and declaring “rails sucks” because they tried to force a square peg into a round hole.
I say all of this because it wasn’t until I attempted to integrate Strada into Jumpstart Android when I realized that Turbo Android was slightly opinionated.
Turbo Android and Convention Over Configuration
Being opinionated about your framework or library is not a bad thing. In fact, I feel being opinionated in software frameworks is a good thing.
Real quick, what is Turbo Android?
In short, it’s a framework that allows you to wrap an existing Ruby on Rails App (or Hotwire enabled web app) with a native shell, and then delegate to native controls when certain actions or routes are intercepted. Maybe you need to have a native photo editor, or video editor. You’d likely do that in native code, not in mobile web code (the web, on mobile, is not that great for such use cases – yet). So, Turbo Android watches URL changes in the WebView and when particular conditions are met (as defined by a configuration.json file) native components will be shown, otherwise it will delegate down to the WebViews. This is the native where you need it approach I’ve spoken about before. Use native components where you need it, otherwise delegate to the web. In my opinion, its the best of both worlds.
Now, back to Turbo Android being opinionated … and why it’s a good thing …
One huge issue with Android itself is that it’s not opinionated at all.
Android is a blank slate empty canvas with no restriction other than “don’t freeze the main thread”.
Everything else is fair game.
People will argue with me here, but this is true. Sure, there are “resources” and “advice” on how to structure your app. But nothing is limiting you from doing something bad. Android is a foot gun of mobile operating systems. You have to be careful because it’s very easy to do something wrong.
What do I mean?
Go put EVERYTHING into your main activity and just do view swapping at runtime. Forget fragments, lifecycle owners, etc. Just use an activity and just swap views. Write ALL of your code in ONE file, views, etc, everything. Nothing will stop you, other than your sanity because of the pain you’ll be in, but this is fair game. There are no rules with Android.
This was very much like Ruby before Rails came out. You could write a web app in Ruby, but there were no rules. When DHH created Ruby on Rails, he wrote it in an opinionated manner, which forced you to make certain tradeoffs and decisions for the betterment of developing maintainable software that was a joy to write. That came with it, rules you had to follow. You can deviate, but it will just make your life more difficult. Just stick with the pattern and your life will be easier.
This is one of the reasons you can open almost any Rails app and be able to somewhat easily navigate around it and find what you need to find (again, there are exceptions to every rule).
How is Turbo Android opinionated and how does this relate to Compose?
Originally, I modeled my work in Jumpstart Android after the demo in Turbo Android. The default implementation of the demo uses an XML Toolbar instead of an AppBar. With some help from some other devs we moved from the Toolbar to the Compose AppBar. Yeah! More modern, win-win, right?!
Not so fast.
Why so?
Well, Turbo Android assumes that you’re using a Toolbar and that makes it easier to work with out of the box.
Additionally, Turbo Android has helper methods to help you get access to the Toolbar. See here. This is very useful when you need to do things with Strada such as adding things to the native toolbar from a web view (yes its possible) or removing things or just interacting with it in general (example 1).
Furthermore, with regular XML views you can get access to View Binding and then you can access the entire tree of views very easily, simply by accessing the binding/inflating/etc. However, if you have a Composable, inside of Fragment via ComposeView, you cannot get the composable tree of objects at runtime because ComposeView does not expose it.
I know, this sounds kind of confusing. Let’s take the prime example of what I needed to do.
With Strada, you can do some cool stuff, such as replacing part of a web view with a native component. As an example, let’s assume you have a HTML button that does something. However, when running in a mobile context you want to add that button to the top right hand side of a native Toolbar and remove the HTML button from the web view.
Why? Well, because that’s a common pattern a lot of mobile apps have, actions go in the tool bar quite often.
Strada allows you to do that (and so much more).
This is what we’re going after:
How Strada Works
When the Android app starts, Strada is bootstrapped and it starts looking for calls from the web that are being made to the mobile app from Strada Web in the WebView. In this case, we have something called a Component in Strada (example below) that gets called when various HTML element attributes are on the screen.
Without getting into the weeds, a native component will get invoked. At that point you can start to do native things as native code has been called. In this instance (the screenshot above) its happening via a FormComponent
, very similar to the one here, which is written in Kotlin, and runs natively in the Android app:
If you inspect that code you’ll see that a menu is being inflated, the Toolbar is being accessed and items are being added to the toolbar dynamically based upon input values from the HTML.
Doing this with the Toolbar is vastly easier than with Compose.
Why the XML Toolbar vs Compose AppBar
Doing the above is possible with Compose, and I did implement it, but it required a bunch of glue and plumbing code to get done with view models, observability, etc. In short, it was drastically more code with Compose than without.
Ultimately, it felt WRONG. I felt like I was shoving a square peg into a round hole.
Why?
With XML views, I can easily gain access to the view hierarchy with a View Binding and then I can easily manipulate the view tree. It’s super easy to work with.
When you’re using a composeView in a Fragment, you cannot get the typed Composable to work with. Therefore, you need to figure out a way to communicate with the composable in a unidirectional manner.
Some will say to just inject a repository of some sort and communicate that way. That’s feasible. However, at its root level that is basically a channeled message bus to pass values to a subscriber from a publisher. The data would be observed (subscribed to) by the UI view model in order to update the Composable in a unidirectional manner. This is not wrong, in fact, it works well. However, for this one simple use case, it simply did not make sense and resorting back to the XML Toolbar simplified the approach, drastically.
I was able to go from multiple levels of indirection and abstraction to simply grabbing a view binding and manipulating it in place.
It was much simpler to use XML Views in this case.
This is not a dig against Compose, but in my opinion XML views are not the right implementation for the job as Turbo Android works seamlessly with the XML Toolbar and is part of the opinionated framework (Hotwire, Turbo, Rails, etc), and Jumpstart Android is a Turbo Android implementation.
To me, its important to be pragmatic and use the right tool for the right job.
The rest of the Compose views I have in the app will remain as compose views.
For a template such as Jumpstart Android, my goal is to create a template that is as easy to understand as possible. Using a Toolbar in this case is just that – much simpler. I still do have the branch available for those who are interested in how to do this with Compose, but the cost vs benefit did not weigh in favor of Compose in this example.
Leave a Reply
You must be logged in to post a comment.