Categories
Mobile Apps Software Architecture Software Development

HyperTrace 1.0 – My First Android Development Experience

This week, my company Danger Interactive LLC launched its first app, one for which all of the programming and art was done by myself, so in a sense, I launched my first app.

Learn more about that here, by the way: https://dangerzonegames.com/apps/hypertrace/

Experience

My preexisting experience with android development was very limited prior to developing this app. I had followed a tutorial one time over five years prior, before I even understood programming well. On another occasion, I planned to build an app for a friend, but once again, this was before my understanding of programming was sound enough to create even web apps. Thus, going into this project, I understood: basic Java programming concepts and how to launch Android Studio (although back when I did tutorials, there was no such program, it was all addons for Eclipse).

I spent approximately 2 months learning the Android API and building this app, which I think is a lot longer than it would take to duplicate it if I were to do it again, now that I know the ins and outs of the Android API and the Java programming language. Android is an excellent and easy to use API, and I am very happy I finally got around to learning it.

My Perspective on Android UI Reusability

In Android, the UI is composed of Activities, which is the main container for all other content. Usually only one activity is visible at a time, although there are ways to display partly transparent activities, but that’s not relevant for this discussion. Inside of these Activities, you have “Views” and “Fragments”. Everything that displays something to the screen is a View, and Fragments are a special kind.

Fragments, at first sight, seem to be an excellent way to create reusable chunks of Views. This is patently false. This is, in fact, what I suspect will be the primary mistake of new Android developers with regard to creating UI layouts.

There are a million reasons for this, but to make a long story short, Fragments are not meant to be a reusable chunk of views, in abstract. They are a special case. Fragments are not direct children of the Activity, and therefore they have an entirely different lifecycle. They are handled by the fragment manager. This separate lifecycle means that you can do really neat things like moving a fragment from Activity to Activity or changing the layout around.

However, if that’s not what you’re doing, then it is going to be a huge pain, because you cannot treat Fragments like regular views. All transactions must happen through the fragment manager. As you might imagine, this could also add additional overhead. So essentially, unless you need this special functionality, there’s a much better choice:

public class ReusableChunkOfLayoutView extends FrameLayout

This is what you should do. You will create a subclass of FrameLayout, which is your reusable chunk of layout, and then you’ll inflate a layout.xml file from that. It’s simple and straightforward, it offers the same ability to create XML layouts and build them easily, and it inherits all of the pre-built onLayout and onDraw code from FrameLayout, which is the rough equivalent of a “div” tag in HTML.

Reactive Code is Neat

I wrote the entire app using “Reactive” API design. What that essentially means is that each component that embodies some kind of state, network, or IO (especially network and IO actually) is capable of communicating with dependent objects. The means that I used to facilitate this communication channel was the Observer pattern.

public interface ObserverInterface {}

ObserverInterface was actually just an empty interface. It’s only there as a syntactic sugar to make the intent of a particular class clear to the programmer.

… public static class Observer implements ObserverInterface

Each class that needs to communicate with other classes creates a static inner class that implements this empty interface

… private SingleObservable<Observer> m_observable = new SingleObservable<>();

Then the class creates an instance of either SingleObservable or MultiObservable, through which all of the notifications will be passed. It is a generic class because ObserverInterface doesn’t offer any methods, so we need to have no erasure in order to be able to call the methods that are added to the observer. This is a good example of functionality via composition instead of inheritance.

public interface ObserverAction<T extends ObserverInterface>

The ObserverAction class represents a “notification” essentially. Once again, it’s generic to avoid the erasure issues. The interface contains a single public method called “run” that takes a single argument of type “T” (the observer). This observer argument is used to be able to call methods on the observer (the real notification channel). The SingleObservable and MultiObservable classes receive these objects and apply them to each relevant observer.

public class SingleObservable<T extends ObserverInterface>

The SingleObservable class was a relatively straightforward one. It just needs to have a single private member that holds one element of type T (the observer), and an ArrayList of ObserverAction objects. The ObserverAction list is there because of what I termed “final actions” which represent actions that should be run on every observer added after the action occurs. This is good for such things as observers with an “onComplete” method, where you can be certain that it will only be called once, and therefore the reasonable assumption is that later observers might want to be informed of this completion.

This class includes a “setObserver” method, and then a “run” and a “runFinal” method, which are provided the ObserverActions to be run once, and run forever, respectively.

public class MultiObservable<T extends ObserverInterface>

The MultiObservable class was a little more complex. There are thread-safety issues that need to be considered in this case. This observable needs two ArrayLists of objects of type T, one of which is the observers, and the other is the pending observers. Pending observers is the holding patter for observers that try to get added while a notification is being processed. Thus, there is a boolean flag that represents whether or not the observers are being notified, and if so, “addObserver” will place the observer in pending, and then the running “runAll” or “runAllFinal” methods will unset the flag and move all of the objects to the observers list and clear the pending observers.

This class includes different methods. There is an “addObserver” method, and then “runAll” and “runAllFinal” which do the equivalent of “run” and “runFinal” in SingleObserver, but they iterate through every object in the observers list and runs the ObserverAction on the current observer. As mentioned, on completion they move anything from pending to the regular observers list.

The Bad… The Ugly…

There are exactly two things that I hated to use when working with Android. And these are them:

Scrolling

To do scrolling, there are three relevant views. There’s ScrollView, HorizontalScrollView, and NestedScrollView. What do you need to know about them? ScrollView is deprecated and useless, just use NestedScrollView instead, HorizontalScrollView is the only scroll view capable of horizontal scrolling, and there is no way to create a single view that scrolls on both axes. You must nest a HorizontalScrollView inside of a NestedScrollView (or vice versa).

Maximum Height/Width

There is no such thing. I have actually not found a good way to do this either. I guess I’ll update anybody on a solution if I ever find one. This is a tragic issue because max-height and max-width are extremely useful when creating responsive layouts. Obviously this is less of a concern on Android, since the screen and window size isn’t expected to change much.

Speaking of responsive layouts, your best bet for creating anything that can be termed “responsive” will likely involve a ConstraintLayout or two. Learn to use them. You won’t regret it.

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *