iOS Tutorial ActivityClass: A cross-platform development class and strategy for b4i/b4a

Jack Cole

Active Member
Licensed User
This is not an official tutorial and is just one option for developing cross platform apps with b4x. I have found it to work very well in creating cross platform apps that share their code base almost entirely. I have developed cross platform apps with other frameworks, but I am the most satisfied with the b4a/b4i solution. If you are designing a new app, you may want to use B4XPages to make a cross platform app. This ActivityClass is best used for migrating existing (large) Android apps to b4i. Advanced Android developers who understand and are comfortable with the Activity lifecycle may also prefer ActivityClass. Additionally, ActivityClass fully supports multiple orientations.

The b4x development system keeps getting better for allowing the sharing of code in cross platform apps. The ultimate wish of most cross-platform developers is to share one code base. You can share a fair amount of code between iOS and Android on the b4x system currently, but it is generally limited to classes and code modules.

I wanted to be able to share more code between b4i and b4a, and the path to porting a b4a app to b4i was not clear to me. As a result, I developed a class in b4i that allows you to run an Android activity module in b4i. I also developed a strategy for developing and maintaining the apps. In b4i, the ActivityClass handles the execution of the relevant Android activity life-cycle subs. It uses a NavigationController in the Main b4i module to handle the UI for the b4a Activity module.

Recommended strategy for developing new cross-platform apps using the ActivityClass for b4i.

1). Start developing with your Android app. For new apps, you could use the b4a starter example that is attached. Keep the code in your Main Activity to a minimum. I recommend using the Main Activity as a splash screen and starting Main2 as your true main Activity. This will allow you to share the code of your Main2 Activity between platforms. On both platforms, use Activity.Width and Activity.Height when adding UI elements in Activity modules rather than the 100%x approach. You can use the % approach in layouts, but you might have problems on iOS if you try to use it in code for adding UI elements.

2). Use the cross-platform XUI views where possible in your layouts. Use SwiftButton instead of the standard button.

3). Try to stay away from libraries or UI classes that are b4a only. Some libraries are OK to use if they are substantially similar across platforms (consider libraries like Firebase, StringUtils, SQL).

4). Use conditional compilation to turn the Activity Process_Globals and Globals into a single sub (see example below). That way, all of your variables will be appropriately available to your code. You will need to add a duplicate of Globals and use it like you do in b4a. Also, make a copy of all variables in Globals to Process_Globals. Globals will be called to reset these variables like when b4a does it (like changing orientation).

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
#if b4a
End Sub

Sub Globals
#end if
    'These global variables will be re-declared each time the activity is created.
    'These variables can only be accessed from this module.
    Private AccountButton As Button
    Private ClearDataButton As Button
#if b4i
    Private Activity As ActivityClass
    Private ActivityPanel As Panel
#End If
End Sub

#if b4i
Sub Globals
    'These global variables will be re-declared each time the activity is created.
    'These variables can only be accessed from this module.
    Private AccountButton As Button
    Private ClearDataButton As Button
    Private Activity As ActivityClass
    Private ActivityPanel As Panel
End Sub
#end if
5). After the Globals sub in shared Activities, add the following code. This code is needed to ensure that the UI events will occur in the Activity module on b4i. This code is called from the ActivityClass. You shouldn't need to modify this for most projects. Collapse the region so you won't be tempted. :)

B4X:
#Region Android Compatibility Helpers for b4i
#if b4i
'shouldn't need to modify this code block in most cases
Sub Set_Activity(ParentActivityClass As ActivityClass)
    Activity=ParentActivityClass
    ActivityPanel.Initialize("")
    Activity.ActivityView.RootPanel.AddView(ActivityPanel,0,0,Activity.Width,Activity.Height)
End Sub
Sub AddView_To_ActivityPanel(ParameterMap As Map)
    ActivityPanel.AddView(ParameterMap.Get("View"), ParameterMap.Get("Left"), ParameterMap.Get("Top"), ParameterMap.Get("Width"), ParameterMap.Get("Height"))
End Sub
Sub LoadLayout(Layout As String)
    ActivityPanel.LoadLayout(Layout)
End Sub
Sub RemoveAllViews
'    LogColor("Main2 RemoveAllViews",Colors.Magenta)
    ActivityPanel.RemoveAllViews
End Sub
Sub TestLog(str As String)
    Log("testlog: "&str)
End Sub
Sub ExitApplication
    Main.ExitApplication
End Sub
#End If
#End Region
6). Add conditional compilation around the Activity Attributes of activity modules. Do the same for service modules.

B4X:
#if b4a
#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region
#end if
7). I have not done a lot with handling the service modules. The current example only executes the Service_Create event. You will probably need to put a conditional compilation around the Service_Start event to make it specific to b4a. I may do more with this in the future.

8). After you write a significant portion of your b4a app, open b4i. In b4i, go to Projects and Add Existing Modules (or select and copy modules and paste them into b4i). You'll want everything except modules that you know are b4a only. I usually link them using a relative path. You want to link rather than copy the files. That way when you make changes to one platform, it will be available for the other automatically.

9). You'll need to replicate the layouts in b4i that you use in b4a. Have the Designers in b4a and b4i open at the same time. Select all the views in b4a (Ctrl-A) and copy them (Ctrl-C). Paste them in the b4i designer (Ctrl-V). You can copy and paste your variant code and compare properties of the views.

10). You'll need to resolve compatibility issues in the linked code modules by replacing incompatible code with code that will work on both platforms or use conditional compilation to exclude incompatible code (like #if b4a). My preference is to use code that will work on both platforms, but that is not always possible.

Example Project Description: The TableExample_cross_platform.zip contains b4a and b4i projects. For the example project, I took an example presented by Erel for b4a for creating a table view based on a scrollview. I moved the code from Main to TableViewActivity so it could be shared with b4i. I added a Main2 activity so you could see how navigation works with multiple activities. The Main activity is used for a splash screen, and to initialize the ActivityClass to start Main2. The example b4i project will automatically start the Starter "service" and Main2 "activity." In b4i, these are now just code modules.

Other notes: The Map_B4X class is used in b4i for keeping an ordered stack of the Activities. I have modified this class from the original (OrderedMap).

Ongoing Development of Your App: Consider that the b4a project is primary and the b4i project is dependent on the b4a project. As such, new modules and features should be added to the b4a project first.

Please let me know if you encounter any issues or if you have ideas for improvement.

Update: 9/19/19 - Revised example and starter project to include new code. These changes make the apps mimic the activity life cycle of Android very closely. See the table example for restoring the activity state on rotation or returning to the TableActivity (click a cell and rotate or leave the activity and return). I renamed the OrderMap class to Map_B4X since I have made so many changes to it.

Update: 9/21/19 - Added handling of Activity_Pause when opening a new activity or calling Activity.Finish. Improved handling of Activity_Create (with the FirstTime variable). Fixed a bug in Map_B4X.

Update: 9/25/19 - Added B4XDrawer starter examples. This will give you a starter app using the B4XDrawer class. Added screenshots for this.

Update: 12/10/19 - Added Color property to the Activity class. Fixed some bugs with the Map_B4X class. Added RemoveAllViews to the code that is included in each activity module (see code for Step 5 above).

Update: 12/29/19 - Added ActivityClass.bas to be downloaded directly on this post. I made a change to have it remove all views when the device changes orientation like Android does. The example projects have an older version of this file that does not have this feature.

Here is a tabber starter project using this class and strategy.

upload_2019-9-3_5-47-25.png upload_2019-9-3_6-52-41.png
 

Attachments

Last edited:

Jack Cole

Active Member
Licensed User
I made a significant update to the class and examples. These changes make the apps mimic the activity life cycle of Android very closely. See the table example for restoring the activity state on rotation or returning to the TableActivity (click a cell and rotate or leave the activity and return). I renamed the OrderMap class to Map_B4X since I have made so many changes to it. You will now also need to include essentially a second copy of the Sub Globals. This allows for the b4i and b4a apps to have the same behavior upon significant events (like change of orientation, exiting an activity, returning to an activity, and so forth).

If there are any Android examples that you would like me to try to port to b4i let me know.
 

Jack Cole

Active Member
Licensed User
I found and fixed a few bugs/omissions.

There was a bug in Map_B4X, so you will need to replace this in your projects.

I missed the step of calling Activity_Pause when starting a new Activity or when calling Activity.Finish. There were some issues with the FirstTime variable in Activity_Create. In an existing project, you can fix this by replacing the code in the Main module and ActivityClass. Copy this from the starter project or example.
 

sorex

Expert
Licensed User
On both platforms, use Activity.Width and Activity.Height when adding UI elements in Activity modules rather than the 100%x approach. You can use the % approach in layouts, but you might have problems on iOS if you try to use it in code for adding UI elements.
I use the percentage method in all my projects and it works fine.

The only thing you need to keep in mind is that the returned values are only correct from in or after the page_resize event.
If you create your views in the Application_Start event it might act weird sometimes as it didn't get the right dimensions yet.
 
Last edited:

Jack Cole

Active Member
Licensed User
I should have been more specific. You might have problems using this class and system on iOS with the % approach. Even though I was careful to call Activity_Create from the page_resize event, I do this with CallSub. It is also in a different module. I had problems when using the % in my projects. I assume it is related to being in a different module and being called by CallSub, but might be wrong.
 

Jack Cole

Active Member
Licensed User
I made a minor update to the class above. It also includes bug fixes to the Map_B4X class.

I have now made extensive use of this method and find it to be solid and an excellent way of leveraging the b4x system for developing cross platform apps. I completed the porting of one app containing 20 activities and nearly 50 total modules. I am finishing up porting my most major app that contains approximately 150 modules and 50 activities.

If you want to try the first major app I ported, here is Word Games for Android. Here is Word Games for iOS. There are no additional modules in the b4i project folder. This means that it is using all of the code from the b4a project. I love it!
 

Marcos Alves

Well-Known Member
Licensed User
I tried the example above and when opening the b4i source code I got the error:
upload_2019-12-11_15-30-20.png


I think that this is happening because some reference to the Android folder is missing, but really don't know how to fix... tried to add an existing module poiting to the Android folder in b4i code but the framework is raising an error ...

What am I missing?
 

Jack Cole

Active Member
Licensed User
I tried the example above and when opening the b4i source code I got the error:
View attachment 86253

I think that this is happening because some reference to the Android folder is missing, but really don't know how to fix... tried to add an existing module poiting to the Android folder in b4i code but the framework is raising an error ...

What am I missing?
What example are you using? I downloaded all of them, and I don't get an error in b4i. Just extract everything in the archive and don't move the folders to start with. The b4i app is sharing the b4a modules using a relative path. If you move the b4i project, you have to move the b4a project along with it. You also can't rename the b4a project folder without sharing the modules again in b4i. I am assuming you have the latest b4i version.
 
Last edited:

Marcos Alves

Well-Known Member
Licensed User
What example are you using? I downloaded all of them, and I don't get an error in b4i. Just extract everything in the archive and don't move the folders to start with. The b4i app is sharing the b4a modules using a relative path. If you move the b4i project, you have to move the b4a project along with it. You also can't rename the b4a project folder without sharing the modules again in b4i. I am assuming you have the latest b4i version.
Thanks @Jack Cole , I was out of the office and will test again to find out what I'm doing wrong... I understand that b4i project uses b4a folders referencing relative paths... I'm extracting the example to the desktop, without to change the paths structure.
 

Kevin Hartin

Member
Licensed User
I made a small update to this class. It now removes all views when the device changes orientation like Android does. You can download the latest ActivityClass.bas on the original post.
Hey Jack, great tutorial. I spent yesterday adding your stuff to my project and apart from slightly similar Main, everything else is common and is compiling, although without common Navigation elements as yet.

One (of many) question though...

If I add my B4A Starter service to my B4i project as a Module, does this get run before anything else as in B4A? It seems to be doing this but I cannot find any reference in the forums.

Is there anything special to be done to make the Starter Service/Module Cross Platform compatible

Thanks,
Kev
 

Jack Cole

Active Member
Licensed User
Hi Kevin,

The starter service gets activated from the Main module here:

B4X:
Private Sub Application_Start (Nav As NavigationController)
    NavControl = Nav
    NavControl.NavigationBarVisible=False
    Starter.Service_Create
Since the Main module shouldn't be used for much other than the splash screen, it shouldn't make a lot of difference that a little bit of code in Main is executed first. The starter service is really not a service on b4i. It is just a code module. The only sub in Starter that is executed at startup by my examples is Service_Create. To make it compatible, you have to use conditional compilation (#if b4a) for Service_Start and the service attributes at the top. I usually only need to move existing code from Service_Start to Service_Create.
 

Kevin Hartin

Member
Licensed User
OK, thanks for that, now it is making more sense why I couldn't find any reference in the forums. I had already done all the conditional coding and it seems to be working as it should.

Thanks and great work.
Kev
 

Kevin Hartin

Member
Licensed User
Hey Jack, I'm back. I did a big port of my Android app to b4i today and it is all working fine with your ActivityClass, however I am a little confused about the whole Page thing in b4i.

I am trying to add the capability to chose a picture to go into an ImageView. In b4A I use the chooser and it works fine. For b4i I think I have to use the Camera object and either SelectFromPhotoLibrary or SelectFromSavedPhotos, however, I have to provide a page when I initialise the Camera.

I have defined and initialised a page called Products but when I try to refer to it when I call the camera, I get the following error;

Warning: Attempt to present <UIImagePickerController: 0x102106a00> on Page (vc): (null) whose view is not in the window hierarchy!

I assume what I need to refer to is already stored in the ActivityPageMap when I load the activity, but how do I refer to it so I can feed the page object to the Camera?

This activity is called with the following;

Main.StartActivity(Inventory,"Inventory")
 

Jack Cole

Active Member
Licensed User
If I understand you correctly, you want to reference the page controller from the Activity you are in. You can do that with Activity.ActivityView. You would do that from within the Inventory Activity. I think I should have given it a better name.

1582734418167.png
 
Top