How to refactor big Alloy controllers

alloy controller

At first, you got a nicely structured Alloy controller, but as you go on, new features keep getting added, and slowly but surely you end up with a monster. Your XML file might still look okay, but your controller file starts getting hundreds of lines and the end is not in sight. Sounds familiar?

How do you restructure such a mess into a well-structured file again without rewriting the entire thing? It is easier than you might think.

There are actually a few steps you can take to make refactoring easy, like moving more UI elements to XML. From there, it is easy to structure your code. Just follow along with a simple one in your code, and then apply that to all of them, one by one, until your code is clean again.

Move eventListeners to XML

The first easy fix is to move anyeventListenerson existing UI elements to your XML file. This saves you quite some space in your controller and gives you a clear overview of which UI components listen for user input. If you have this now

$.myButton.addEventListener('click', myFunction);

You should replace it with this:

<Button id="myButton" onClick="myFunction" />

Any event is always preceded with on and then your event starts with an uppercase character. The rest of the casing on the event should remain the same.

If you have an anonymous function like this

$.myButton.addEventListener('click', function(e) {});

Then you should first name your function, I usually give the functions a predictable name which is structured like handle[id][eventName], like this: handleMyButtonClick(), and then you can again move the eventListeners to your XML file.

If you have a UI element that sets a different listener based on a certain condition, then you need a new function that handles all these conditions. For example, if you have a Button that either checks or unchecks something, create a function, and perform the needed logic in that function to determine if you need to do a check or uncheck. By creating multiple event listeners for the same UI element you’re creating more complications in your code which makes things much harder to debug.

There is absolutely no need for two listeners. And in the case you have certain listeners that do nothing in certain situations, like a button click on a greyed-out button, then again, do this verification inside the handler function, do not add or remove listeners on the fly.

You can even store state in the button element itself, as custom properties on any UI element are allowed. So you could set $.myButton.hasDisabledState = true and read this property inside your click handler. These custom properties can also be set in the XML and
XML and tss files.

Move Ti.UI.create() to XML

You should move any
Ti.UI.create()
method to your XML. You don’t need this code in your controller file. Make this a rule for your future-self as well, whenever you feel the urge to write
Ti.UI.create()
ask yourself if it is needed. 99% of the cases it is not and only adds complexity to your app logic.

Another example: after you’ve clicked a button a TextField should appear. Do you need to create this textfield on button click? Or is it fine to have it already created, but hidden (height: 0, clickEnabled : false ) and just animate it to visibility when the need arises.

Another example: after clicking a button, an API call is initiated and the results are displayed on the screen. Yeah, no way should you do Ti.UI.create() here, you should probably do a Alloy.createController() with the data, and have that controller make the UI for you.

If it is a list, you probably want to use data-binding with an instance collection in that controller. If it is a single element you can have your UI ready in the XML and just apply the data to the right UI elements by setting the values based on the result like you always do
$.nameLabel.text = $.args.data.name ,
or use an instance model with data-binding, whatever your preference is. The quickest way to do either is this small section of code:

$.wrapper.add(
    Alloy.createController('mySubController', 
        {data: data}
    ).getView()
);

And lastly, something I’ve done myself in the past frequently as well is to create alertDialogs
inside the controller file, and not in XML. At a first glance it makes sense to do this, but do you really want that? If you put them in your XML you have them ready to go, you can bind eventListenersstraight from there, there is no need to account for memory leaks, and you got a clear overview of your alertDialogs. You can even structure them nicely and style text inside your tss files, and whenever you need them you can just call $.myDialog.show() and you’re ready to go.

And just in case you need to display a dynamic message in your alertDialog, there still is no justification for not putting them in your xml, you can just adjust the text with
$.myDialog.message = message
.

Identifying UI segments

Now comes the most important part of refactoring. You should now identify segments in your UI. If I’d ask you to point out isolated segments in your app that have their own functionality, I’m sure you could find some on this complicated page. Examples could be a TextField with validation rules or other interactive elements, a slider that stores its result in a property, a Button with double-click prevention that opens a new window, etc. Basically, any UI component in your app that has interaction/interactivity or that is complex. If it is a complicated page, I’m sure there are several of these.

Any of the elements I mentioned earlier in this blog post would fit the description of a UI segment typically as well.

So what you’re going to do, now that you have identified one, or several segments, is to move them out to a different controller.

Let’s say, you got something like this:

<View id="wrapper>
  <ImageView id="img" />
  <Button id="toggleImageButton" onClick="handleToggleImage" />
  <Label id="imageDescription" />
</View>

And let’s just imagine the event handler downloads a new image, replaces the one present now, updates the description, and disables the button for a bit (for whatever reason). Because of the image download and handling of it (including failures) you probably have like 50 lines of code for this, as well as quite some styling rules.

What you should do with this code, is make a new Alloy controller. We’re going to name this /segments/replaceableImage (yes I’ve put the controller in a sub-directory for structure). We’re moving all relevant xml/js/tss functionality to this file, and then back in your big controller we’re going to put this instead

<Require src="/segments/replaceableImage" />

Now, we’re done. This should work out of the box already. You’ve basically made a reusable component. If you need any interactivity from this component to your main controller you can do this with exported functions or (custom) events. You can read more about this on this blog post I’ve written previously on medium. However, a short gist of how it would work:

<Require src="/segments/replaceableImage" onCustomEvent="handleImageEvent" />

Then, in your replaceableImage controller:

$.trigger('customEvent', payload);

As you can see, the same rule applies to events here as well, add “on” in front of your event name, and upper-case the first character of your event.

You can repeat this effort a bunch of times until you are satisfied with the segmentation and the structure of your app.

You can even come to the conclusion you can segment further on a just created segment, or discover you actually have duplicated code and can now simplify by reusing the same element. And this is where customization and passing through arguments come in.

Passing through arguments to your UI segment

When creating segments you typically will want to customize them without adding extra code. Like setting a button title, or an image path. Luckily, since Alloy 1.14 (released mid-2019), you are easily able to.

If we take the XML from above, we can add a custom property to it.

<Require 
    src="/segments/replaceableImage" 
    onCustomEvent="handleImageEvent"
    imageURL="https://example.com/myimage.png"
/>

Of course, you can also set this property in tss, and in tss you even have to ability to nest data or structure it (with objects), but that is off-topic right now.

Now, inside this replaceableImage segment, you can access your custom property directly in XML. As you remember we have an ImageView inside it. There are two ways of setting this property. One is via your controller code, which can add extra logic where needed, but since Alloy 1.14 you can set this property directly in XML like this

<ImageView id="img" image="$.args.imageURL" />

Yes, that’s right, just set the argument straight into the XML, and Alloy will parse this correctly for you. Any custom property you set in XML or tss will come into your controller inside the $.args object, so make use of this!

The pre-Alloy 1.14 version of the above, or of course when you want to add some logic to things, is to do something like this:

$.img.image = $.args.imageURL;

Conclusion

In this blog post, I have illustrated a couple of improvements you can do to simplify and clean up your code, without having to restrict yourself in any way. Of course, there are many more improvements you can take but these should be enough to clean up your massive alloy controllers, or of course prevent them from becoming massive in the first place.

Read more about Alloy and creating custom tags in Titanium with Alloy.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.