This post was initially published for AppInventor classic, but currently sources are available also for AI2. Unless explicitly noted, comments and images correspond to the classic version but the code is roughly the same.
I have to admit that the method we’re going to explain is not so advantageous in AI2 because now it’s possible to edit several screens at development time, which was one of the two main weak points in AI classic multiscreen. Anyway, the drawback of not being able to share variable and methods remains, and I think that’s enough reason to use this method is most of our projects.
This post demonstrates a method for managing multiple windows in App Inventor based on hiding and showing arrangements from the same screen. I admit the title may be controversial, but I’m really convinced that this is the most effective way to manage multiscreen in the AI current state of art. Anyway I understand this is arguable so if you’re not comfortable with the title just think of this tutorial as an “alternative method to manage multiscreen” which you could consider in some of your apps.
What are we going to build
As we know, release 42 of AI added support for multiscreen, recognizing it as an absolute necessity for any app that goes beyond a simple prototype. So before release 42 we were forced to simulate multicreen by showing and hiding controls. And now I’m proposing to go back to those times (!). Well, let me clarify it. What I’m proposing to do is avoid creating a separate screen for every window you want to show. The reason is that current AI screens have two main drawbacks:
- You cannot reuse anything among different screens. In other words, you cannot share functions and controls that are common to all screens (you’re forced to replicate them in all the screens). For variables this is somehow mitigated because it’s possible to pass values, but for functions it’s really tedious and a great source of quality problems. We’re still lucky to count on the excellent copier facility provided by the App Inventor Repository, but this helps only when creating the app and not for its further maintenance (which consumes 80% of development time in traditional environments and probably more in AI)
- You cannot switch forms while testing in the development environment, so it’s not possible to test interaction among screens (you need to generate a version each time)
By including several windows in a single screen we avoid both problems: you can test the windows interaction while developing and you can have a shared version of variables, functions and components that are common to all windows. It has also drawbacks, from which I’d remark two:
- The design task is more complicated because all the windows are mixed and it’s harder to see the real appearance of each one
- You have to manage the transitions between screens (particularly the back button)
For the first issue I recommend to mark all windows as non-visible except the one we’re working on. This way we can concentrate successively in the different windows and work in the same conditions as if we were using separate screens. Hidden windows are not a problem for testing because they are appropriately shown by code (as we’re going to see).
Regarding the second issue, that’s precisely why I’ve written this post. We’ll see a method to handle windows transitions in a systematic way. It may appear complex but we must take into account that part of this code would be necessary anyway if we used separate screens. Moreover, it would be replicated in every screen. On the whole, the code for the same app would be considerably longer (I know it very well because the app was initially written with separate screens!).
How it works
The key for simulating the screen transactions is a well known pattern called stack (technically a LIFO stack, which stands for “Last In First Out”). For those who have been around the programming world for some years this pattern leads us back to the hard origins when we were forced to program in machine code primitive computers such as Sinclair Spectrum. The pattern idea is to pile up an undetermined number of events (in our case the windows shown) but at any point in time we need only to be aware of the last event. The solution consists in using an infinite stack (which in AI can be implemented with a list) where we add and remove elements, and maintaining a pointer (a numeric variable in AI) indicating which is the last element from the stack.
This is more clearly shown with an example. Imagine the user logs into the application, pushes the Register button and from here the Help button. Then he presses twice the back button which must bring him back to the login window. The following image depicts the contents of the stack after each event, the red element indicating the currently shown window:
As we can see, after pressing the back button we must simply remove the top element and get the new top in order to locate the window to show.
You can try the application by downloading these sources and uploading them into App Inventor. You’ll realise there’s some functionality that is not explained in this tutorial because this is actually a more general application (which is explained in the Login template post). The part that concerns us is the way to implement seven windows using just two screens.
Each window to show must be simulated by means of a TableArrangement component. If this kind of arrangement is not suitable for the layout required in our window we can simply add the right arrangement under the TableArrangement. For example, the following image shows a window that requires two arrangements (Horizontal and Table):
Therefore our graphical design task consists in distributing each component into the TableArrangement where it must appear. The Arrangements’ Width and Height attributes will typically be set to “Fill parent”.
- SCR_LOGIN, SCR_REG, SCR_SEND, SCR_HELP, SCR_GOOGLE, SCR_MAIN: We associate a numeric value to each window in order to make the code more readable (e.g. the sentence “CurrentScreen = SCR_MAIN” is much easier to understand than “CurrentScreen = 1”
- lstStack: List of open windows. This is the stack used to control the back navigation
- iStackPointer: Pointer to the current screen within lstStack
- lstScreens: List of TableArrangements used as containers to simulate windows. The fact that all elements in the list have the same type allows to make generic calls to obtain an element and show it, which is the key for the pushScreen, popScreen and ShowScreen auxiliary functions
- Screen1.Initialize: The only meaningful part is the call to function initScreen and pushScreen. Other sentences are not used in this tutorial
- initScreen: Initializes the lstScreens variable with the table arrangements used to simulate windows. The order is very important, because the table correspondiing to each window is located by position in this list. For example, to obtain the Register window we must use the sentence:
SELECT LIST ITEM lstScreens, SCR_REG
The mechanism to open and close windows is as simple as it would be with screens. Here are a couple of examples:
- ButtonRegister.click: Any action that requires opening a window needs just to call the pushScreen function passing as a parameter the constant value assigned to the window
- Screen1.BackPressed: Call the popScreen in order to remove the top window from the stack and show it. If the stack is empty close the application
Of course for this simplicity to be possible we need the auxiliary functions popScreen, pushScreen and those related.
- pushScreen: This function receives as a parameter the window to open. It adds the window to the stack, hides the current window and shows the new one using the ShowScreen auxiliary function
- popScreen: Obtains the window that is on top of the stack and hides it. Then removes it from the stack, obtains the previous one and shows it using the ShowScreen auxiliary function. It returns the currently visible window
- getCurrentScreen: obtains the screen that’s currently being shown (i.e. the one on top of the stack). This function is called from the initHelp function in order to initialize the help texts taking into account the current window. The function returns directly the iStackPointer value so it does not save space but using a function is smarter and eases evolution in case the stack implementation changes some day
- ShowScreen: This function receives as a parameter the index of the window to show. It then retrieves the associated TableArrangement from the lstScreens list and makes it visible. That’s all regarding the graphical stuff, but this function is also the equivalent to the screen’s Initialize event so it’s the place to handle specific initialization events. In this case:
- Enable a timer in case we’re showing the Google window (and disable it otherwise)
- Hide the top bar in case we’re showing the Google or Help window
- Show the Profile button only in case we’re showing the Main window
- ShowMain: This function is called to show the main menu. An important aspect to remark is that once in this window the back button is not supposed to go to the previous window but rather to close the application, so the function empties the stack before pushing the Menu window into it.