Android Tutorial Edge Case use for B4XPages: Changing Orientation for Many Pages

This is for advanced users only. Please see Erel's important cautions about using this approach below.

This post and examples demonstrate how to extend B4XPages to allow orientation changes. It should be considered a beta version, since you may encounter some problem that I haven't yet discovered.

Background: B4X continues to improve its cross-platform support. With the advent of B4XPages, you can share almost all of your code across platforms and/or develop single platform apps with a simpler lifecycle. As a design feature of B4XPages, there is a limitation that requires you to be locked to one orientation. There are some good rationales for this such as keeping the app lifecycle simple and trying to keep with some Android platform recommendations. This is probably a best practice for most people.

As for me, my apps support both / changing orientation. My philosophy is that it is better to enable features that a signification percentage of users desire or will be frustrated by the lack of the feature. In my experience, this condition exists for many users of my apps (they want to be able to change orientations). I try to reduce friction for my users where I can.

I have also written a custom class that is used more for porting B4A apps to B4i. It replicates many of the same features of the Android Activity on iOS. It may be easier in some cases to use the ActivityClass if you have a large existing Android app that you need to have the ability to change orientations. If you are developing a new app or have an existing iOS app, it may be easier to use the procedures I outline below. Note: if you are OK with being tied to one orientation, you should not use this technique. It is a little more complicated to manage the code than it is for a standard B4XPages project.

How To Use:

1. Required Libraries


Your Android project must include the IME and JavaObject libraries. These libraries are used for catching the HeightChanged event. There is also a manifest line that prevents the activity from being destroyed when the orientation changes.

2. You must set #FullScreen: False

This is in the main module on Android. This is because the IME library does not work correctly otherwise.

3. Adding a new page.

You can add a new page just like you normally would. Remove the Initialize and B4XPage_Created subs. These will be replaced in the code below. Paste the following code into the new page after removing those two subs.

B4X:
#Region PageEvents
'-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
'It is recommended that you not modify the code
'below under most circumstances.
'-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
Public Sub Initialize
    Return Me
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
#if not(b4j)
    LogColor($"B4XPage_Created"$, Colors.Green)
#end if
    FirstTime=True
    Root = Root1
#if b4a
    LastOrientationPortrait=(Main.ActivityHeight > Main.ActivityWidth)
    B4XPage_Resize(Main.ActivityWidth, Main.ActivityHeight)
#end if
End Sub


Private Sub B4XPage_Resize(Width As Int, Height As Int)
#if not(b4j)
    LogColor($"B4XPage_Resize(Width=${Width}, Height=${Height})"$, Colors.Green)
#end if
    Dim AlreadyRestored As Boolean
    If LastOrientationPortrait=(Width>Height) Or Root.Width<>Width Or Root.Height<>Height Or FirstTime Then
        If Not(FirstTime) Then UI_Save_State
        UI_Create(Width, Height)
        If Not(FirstTime) Then
            UI_Restore_State
            AlreadyRestored=True
        End If
    End If
    If Not(FirstTime) And Not(AlreadyRestored) Then
        'save state
        UI_Save_State
        'restore state
        UI_Restore_State
    End If
    LastOrientationPortrait=(Height>Width)
    FirstTime = False
End Sub

Sub B4XPage_Disappear
#if not(b4j)
    LogColor($"B4XPage_Disappear"$, Colors.Green)
#end if
    'save state
    UI_Save_State
End Sub

#if b4a
Sub B4XPage_Appear
    LogColor($"B4XPage_Appear"$, Colors.Yellow)
    If LastOrientationPortrait=(Main.ActivityWidth > Main.ActivityHeight) Then B4XPage_Resize(Main.ActivityWidth, Main.ActivityHeight)
End Sub
#end if

'-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
'Feel free to edit the code below.
'-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
#End Region


Sub UI_Create(Width As Int, Height As Int)
#if not(b4j)
    LogColor($"UI_Create Width=${Width}, Height=${Height}"$, Colors.Magenta)
#end if
    Root.Width=Width
    Root.Height=Height
    Root.RemoveAllViews
'You will generally want to keep the 3 lines above and edit below.
    Root.LoadLayout("Page2")
End Sub

Sub UI_Save_State
#if not(b4j)
    LogColor($"Saving State"$, Colors.Magenta)
#end if
    ToSave_Text=B4XFloatTextField1.Text
End Sub

Sub UI_Restore_State
#if not(b4j)
    LogColor($"Restoring State"$, Colors.Magenta)
#end if
    B4XFloatTextField1.Text=ToSave_Text
End Sub

The region PageEvents contains the code that manages changes to orientation and calling the three important subs at the end. You will generally not want to change that region.

Add the following to Class_Globals. This is used for tracking whether the page is being created for the first time. It also contains LastOrientationPortrait, which is self-explanatory.

B4X:
Private FirstTime As Boolean = True
Private LastOrientationPortrait As Boolean

4. UI Events

The three subs at the end are where you will be working to start. Below that, you can add any other code as you normally would.

UI_Create - This is where you create your UI. It is best to use this sub for creating your UI, because it will have accurate dimensions on iOS. This sub is called when the page is first created and when you change orientation (or any circumstance where the size is changed -- like resizing the window in B4J).

UI_Save_State - This is where you will want to save the state of your UI (for example any UI changes that have happened after it was first created). Save these values to global variables.

UI_Restore_State - This is where you restore changes to your UI. In the example above, it restores the text to a B4XFloatTextField. That way if the app changes orientation, it can restore any changes that the user has made.

That's it! I have tried to keep it pretty simple, and I think it is simpler than the typical Activity lifecycle on Android. Please let me know if you encounter any issues or have recommendations. I hope you find it useful.
 

Attachments

  • ezgif.com-gif-maker.png
    ezgif.com-gif-maker.png
    244.4 KB · Views: 701
  • b4xpages_orientation_example.zip
    203.2 KB · Views: 579
Last edited:

Robert Valentino

Well-Known Member
Licensed User
Longtime User
I think I will try your way. I know that a lot of my users like to be able to flip their phone or tablet around.
I move fields around the screen (based on device size) to make everything look nice (I know totally against designer standards) but I was doing this long before

Will let you know how I do.
 

Robert Valentino

Well-Known Member
Licensed User
Longtime User
One thing I noticed is that after a rotation 100%x and 100%y are still reporting the Initial values.

Anyway to fix this?
 

Robert Valentino

Well-Known Member
Licensed User
Longtime User
I made some changes
B4X:
Public Sub Initialize as Object                                                          '  Changed by BobVal compiler complained
    Return Me
End Sub


Private Sub B4XPage_Resize(Width As Int, Height As Int)
#if not(b4j)
    LogColor($"B4XPage_Resize(Width=${Width}, Height=${Height})"$, Colors.Green)
#end if
    Dim AlreadyRestored As Boolean
    If LastOrientationPortrait=(Width>Height) Or Root.Width<>Width Or Root.Height<>Height Or FirstTime Then
        If Not(FirstTime) Then UI_Save_State

        '  Changed by BobVal  - If you do something in UI_Create that waits this code rolls on and and FirstTime is cleared 
        '                 and UI_Create may not have finished using it
        wait for (UI_Create(Width, Height)) Complete(RC as Boolean)    
                                                                                                                     
        If Not(FirstTime) Then
            UI_Restore_State
            AlreadyRestored=True
        End If
    End If
    If Not(FirstTime) And Not(AlreadyRestored) Then
        'save state
        UI_Save_State
        'restore state
        UI_Restore_State
    End If
    LastOrientationPortrait=(Height>Width)
    FirstTime = False
End Sub

Sub UI_Create(Width As Int, Height As Int) As ResumableSub ' changed by BobVal
#if not(b4j)
    LogColor($"UI_Create Width=${Width}, Height=${Height}"$, Colors.Magenta)
#end if
    Root.Width=Width
    Root.Height=Height
    Root.RemoveAllViews
'You will generally want to keep the 3 lines above and edit below.
    Root.LoadLayout("Page2")

    return true   ' changed by BobVal
End Sub
 

LucaMs

Expert
Licensed User
Longtime User
I downloaded the sample project.

In Activity Main:
B4X:
'...
    ime1.Initialize("ime1")
    ime1.AddHeightChangedEvent
    Dim jo As JavaObject = Activity
    jo.RunMethodJO("getContext", Null).RunMethodJO("getWindow", Null).RunMethod("setSoftInputMode", _
     Array As Object(0x20))
    ActivityParent = jo.RunMethodJO("getParent", Null)
'...

Will IME and JavaObject work in B4i?
 

bparent

Member
Licensed User
Longtime User
Any solution is good if it helps the developer gets his or her job done.

There is no magic. You will gain support for orientation changes but it will make other things more complicated.
Believe me that I'm aware of the possibility of using configChanges to prevent the activity from being destroyed and chose not to use it with B4XPages for good reasons :)


Once you start using it you will see how simple things are when you don't need to worry about the UI state and that you can treat other pages and their views as regular classes and objects with no special life cycle.

Bottom line: most users should use the default B4XPages. If there is any specific "page" that needs to be displayed in other orientation or support multiple orientations, then you can use an additional activity outside of B4XPages.
If the whole app should support multiple orientations and you want to build a cross platform solution then this solution can be a good solution.

To avoid confusing other members when posting questions about this implementation, please don't call it B4XPages. This is not B4XPages. It breaks many of the fundamental assumptions behind B4XPages.
Erel,

Is there a sample project with source code that illustrates this: Bottom line: most users should use the default B4XPages. If there is any specific "page" that needs to be displayed in other orientation or support multiple orientations, then you can use an additional activity outside of B4XPages."

Thanks.
"
 

AnandGupta

Expert
Licensed User
Longtime User
Erel,

Is there a sample project with source code that illustrates this: Bottom line: most users should use the default B4XPages. If there is any specific "page" that needs to be displayed in other orientation or support multiple orientations, then you can use an additional activity outside of B4XPages."

Thanks.
"
Let me give a hand here.
Take the 3-pages sample and instead of .showPage("Page 3") do StartActivity("Page3")
Add the new activity Page3 in your project first.

Hope that helps.
 

Jack Cole

Well-Known Member
Licensed User
Longtime User
I downloaded the sample project.

In Activity Main:
B4X:
'...
    ime1.Initialize("ime1")
    ime1.AddHeightChangedEvent
    Dim jo As JavaObject = Activity
    jo.RunMethodJO("getContext", Null).RunMethodJO("getWindow", Null).RunMethod("setSoftInputMode", _
     Array As Object(0x20))
    ActivityParent = jo.RunMethodJO("getParent", Null)
'...

Will IME and JavaObject work in B4i?
The main module is different in the b4i app. IME is not used in b4i.
 
Top