Insights
/
Engineering
Engineering

MOE Tutorial 1: Create Simple List on iOS and Android Apps Using Multi-OS Engine

Multi-OS-Engine – MOE for short – is an open source framework, which enables to write iOS and Android application in Java. The most interesting thing is...

Ewelina Bukowska
Ewelina Bukowska
April 24, 2017
Updated
Android emulator and iPhone simulator side by side, both showing the same Repositories list of Snowdog GitHub projects built with Multi-OS Engine
Key takeaways
On this page

Multi-OS-Engine – MOE for short – is an open source framework, which enables you to write iOS and Android application in Java. The most interesting thing is that created apps are compiled to native code and they use common code in Java. It’s saves a lot of time and it’s good idea for Android Developers, who want to have iOS app also. MOE was acquired by Intel in 2015. It’s got good community support and it develops fast. About a month ago new MOE version 1.3 was released. It brings a lot of changes. Unfortunately, existing tutorials hasn’t been updated yet, so we decide to write a series of tutorials, which show step by step, how to create simple MOE app.

In this tutorial we will show you how to create MOE application, which fetches the SnowdogApps repository list from the GitHub API (opens in a new tab) and show them in RecyclerView on Android and UITableView on iOS app. It’s quite long, but it describes step by step how to create whole application including iOS views in Xcode. It takes about 30 minute to read it all. If you don’t have experience with MOE and Xcode, you will need about 2 hours to implement it. Article consist of 4 main parts. First of them shows how to create MOE project with Android Studio. The second part is focused on main application logic, it shows how to connect Retrofit and RxJava to a common module. Third section is the most detailed, it presents how to create iOS views with Storyboard in Xcode. The last one is the shortest, it describes how to connect Android Activity with common module. We also include a table of contents to make the article easier to navigate. This project uses Xcode Version 8.3, Android Studio version 2.3 and MOE SDK version > 1.3.2.

The source code is available on our GitHub repository MoeAppSample on branch Tutorial_1 (opens in a new tab). Finished app is shown on the screens bellow:

The finished sample app on Android emulator and iPhone simulator, both listing the same Snowdog Magento repositories

Contents

  1. Set up environment
  2. Create project and set dependencies
    1. Android module
    2. Common module
    3. iOS module
    4. Add dependencies
  3. Write shared part
  4. Create iOS Views
    1. Create views in Xcode
    2. Write code in Java
  5. Create Android Views
  6. Summary
  7. Other external links

Set up environment

Before you start working on MOE application you should check prerequisites. You can find them in documentation (opens in a new tab). Next you need to add MOE plugin to Android Studio. Go to Android Studio → Preferences → Plugins and type Multi-OS and install them.

Android Studio Preferences showing the Multi-OS Engine Plugin selected in the Plugins list

After installation you are ready to start writing your first MOE application.

Create project and set dependencies

MOE applications should have 3 modules. Two of them consist only views for each platform and one module is shared between views. Android and iOS views cannot see each other. They should have only methods, which are responsible for UI and should be as small as possible. The Model-View-Presenter architectures recommended to write clean code and separate views.

Android module

1. First of modules is the app. This is regular Android application. Let’s create it. Open Android studio and create project.

Android Studio Create New Project dialog with application name MoeSampleApp and company domain snowdog.pl

2. Select Empty Activity

Android Studio Add an Activity to Mobile dialog with Empty Activity selected

Common module

Second module called common contains all application’s logic and it’s shared between Android and iOS modules. Let’s create it.

1. Right click on main project directory and select New → Module

Android Studio context menu with New, Module selected, the first step in adding the common module to the MoeSampleApp project

2. Select java library:

Android Studio Create New Module dialog with the Java Library template selected for the shared common module

3. Change module and package name:

Java Library settings in Android Studio with library name common and Java package name pl.snowdog

iOS module

1. The last module called ios contains all ios code written in Java and entire Xcode project. To generate this module, right click on project directory and select New → Multi-OS Engine Module

Android Studio project context menu with New, Multi-OS Engine Module highlighted to generate the ios module

2. Select Single View Application and click Next

New Module dialog with Multi-OS Engine selected and the Java, Single View Application template highlighted

3. Fulfill information required to create Xcode project.

Multi-OS Engine new project form: project name MoeSampleApp, package pl.snowdog, organisation Snowdog, bundle ID pl.snowdog.MoeSampleApp

4. Change module name to ios and click Finish

New Module dialog with the module name set to ios before clicking Finish

Wait until Android Studio generate module. This may take a while. Don’t interrupt.

Add dependencies

1. We’ve got now 3 independents modules. Our purpose is to enable Android and iOS views to see common module. To do this, right click on common module and select Open Module Settings

Android Studio context menu on the common module with Open Module Settings highlighted

2. On the left section select ios, click + on the bottom of window and select 3 Module Dependency

Project Structure dialog for the ios module with the add menu open and Module dependency selected

3. Select common and accept. Next select app on the left section and switch tab to Dependency. Click + and add dependency to common module as previously.

Project Structure dependencies tab for the app module, now listing :common alongside the Android support libraries

Write shared part

We are ready to start writing our application.

1. Add dependencies for RxJava and Retrofit 2 in common module in build.gradle file :

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxjava:1.1.6'

2. Add dependencies for RxJava Android adapter in app module in build.gradle file:

compile 'io.reactivex:rxandroid:1.2.1'

3. Next create model class in common module:

package pl.snowdog.model;

public class Repository {

   private int id;
   private String name;

   public int getId() {
       return id;
   }

   public String getName() {
       return name;
   }
}

4. Add Retrofit interface:

public interface GitHubApi {
   @GET("orgs/SnowdogApps/repos")
   Observable<List<Repository>> getRepos();
}

5. Create Retrofit service generator:

public class ServiceGenerator {

   private static final String BASE_URL = "https://api.github.com/";

   private static Retrofit.Builder builder =
       new Retrofit.Builder()
           .baseUrl(BASE_URL)
           .addConverterFactory(GsonConverterFactory.create())
           .addCallAdapterFactory(RxJavaCallAdapterFactory.create());

   public static <S> S createService(Class<S> serviceClass) {
       Retrofit retrofit = builder.build();
       return retrofit.create(serviceClass);
   }
}

6. Then create presenter which sends request and fetches data from response.

public void getRepositories(Scheduler postThread, Scheduler subscribeThread) {
   GitHubApi gitHubApi = ServiceGenerator.createService(GitHubApi.class);
   gitHubApi.getRepos()
       .subscribeOn(subscribeThread)
       .observeOn(postThread)
       .subscribe(new Subscriber<List<Repository>>() {
           @Override
           public void onCompleted() {
           }

           @Override
           public void onError(Throwable e) {
           }

           @Override
           public void onNext(List<Repository> repositories) {
               mView.showRepositories(repositories);
           }
       });
}

7. Create IRespositoryView – simple interface, which is responsible to send downloaded data to Android and iOS views.

public interface IRepositoryView {
   void showRepositories(List<Repository> allRepositories);
}

Create iOS views

1. Shared Java code of application is ready, so we can start with creating views. First, we need to delete ui package with generated class.

Android Studio project tree showing the ios module with java, pl.snowdog, a ui package and the Main class

Create views in Xcode

2. To start working with iOS views right click on ios module and select Open project in Xcode If it doesn’t work, make sure that you use at least version 3.3 of Gradle. You can check them in file . It should look like this:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

3. When Xcode will be started click on MoeSampleApp and select New file

Xcode project navigator context menu on the MoeSampleApp group with New File selected

4. Select Cocoa Touch Class and click Next

Xcode new file template chooser with Cocoa Touch Class selected under iOS Source

5. Name class RepositoryListViewController, select subclass UIViewController, don’t create XIB file and select Language Objective-C

6. Choose directory /xcode/MoeSampleApp and check target MoeSampleApp and click Create

Xcode new file options: class RepositoryListViewController, subclass of UIViewController, no XIB, language Objective-C
Xcode save dialog choosing the xcode/MoeSampleApp folder with the MoeSampleApp target checked

7. Next select Main.storyboard in project files on the left.

Xcode project navigator with the new RepositoryListViewController .h and .m files and Main.storyboard selected

8. Make sure that you have checked appropriate icons (red rectangle the on screen). Next delete existing View Controller and drag and drop new View Controller

Xcode storyboard editor with an arrow showing a new View Controller dragged from the object library onto the canvas

9. On the right panel check Is Initial View Controller

Xcode attributes inspector for the View Controller with Is Initial View Controller ticked

10. Switch icon on right panel (red rectangle), in section Custom Class set class RepositoryListViewController

Xcode identity inspector with Custom Class set to RepositoryListViewController for the storyboard scene

11. Drag and drop table view inside View Controller. Resize Table View to fulfill entire View Controller.

Xcode storyboard with an arrow showing a Table View dragged from the object library into the View Controller

12. Drag and drop Table View Cell inside Table View. Select Table View Cell in Repository List View Controller Scene. On the right panel set identifier to repositoryItemCell and select Basic from Style combobox.

Xcode attributes inspector for the Table View Cell with style Basic and identifier repositoryItemCell

13. Select Repository List View Controller, click Resolve Auto Layout Issues (small icon with triangle inside – red rectangle on the screen ) and click Reset to Suggested Constraint

Xcode Resolve Auto Layout Issues menu with Reset to Suggested Constraints selected for the Repository List View Controller

14. Switch to Assistant Editor ( two crossed circles – square on the top right corner), select RepositoryListViewController.h file. Press ctrl and drag tableView between @interface and @end . Name it as repositoryTableView , set connection to Outlet and Type UITableView. Click connect.

Xcode Assistant Editor with ctrl-drag from the table view creating a repositoryTableView outlet of type UITableView in the header file

15. Now we have to connect dataSource and delegate to tableView. To do that come back to Standard Editor and in Storyboard press ctrl and drag tableView to yellow circle icon with white squared inside. In Outlets dialog select dataSource and delegate.

Xcode storyboard with ctrl-drag from the Table View to the view controller icon, showing the dataSource and delegate outlets

16. To check the connection, right click on Table View in Storyboard. It should look like on the screen.

Xcode connections panel for the Repository Table View showing dataSource and delegate outlets connected

17. Add Navigation View Controller. In Storyboard select on Repository List View Controller. Then in Xcode menu click Editor → Embed In → Navigation Controller

Xcode Editor menu with Embed In, Navigation Controller selected for the repository list scene

18. Rename title in Navigation Controller. Click on Navigation Item and rename title of it.

Xcode storyboard with the navigation item title set to Repositories above the table view

19. Working with Xcode is already finished. Now it is necessary to bind created UI to rest of apps written in Java. To do that open Android Studio. Right click in ios module, select Multi-OS Engine Actions → Create New Binding named nbc file RepositoryListViewController , next select green +, select Header option and named again RepositoryListViewController .

Android Studio context menu on the ios module with Multi-OS Engine Actions, Create New Binding highlighted

20. Set Header path to xcode/MoeSampleApp , Base package name to your project package, in our sample there is pl.snowdog and in Import headers section write #import "RepositoryListViewController.h"

Multi-OS Engine binding editor for RepositoryListViewController with header path xcode/MoeSampleApp and package pl.snowdog

21. Next click settings icon and choose Generate Bindings

Multi-OS Engine binding editor with the settings menu open and Generate Bindings selected

Wait until you see message on the bottom

Android Studio notification reading Generate successful from the Multi-OS Engine tool

Write code in Java

Fulfill table view with sample data

Generate binding finished successfully. In ios module in java→{package}→src there is generated file, called RepositoryListViewController.java . Inside them we can see generated native Objective-C methods. Android Studio tagged them as errors, but they are only warnings and might be ignored. Here we will write all Java code responsible for UI behaviors.

1. First of all, it is necessary to add some annotation on the top of class definition:

@Runtime(ObjCRuntime.class)
@ObjCClassName("RepositoryListViewController")
@RegisterOnStartup

Let’s start with fulfilling UITableView with sample data.

2. First implement necessary interfaces. For UITableView there are UITableViewDataSource , UITableViewDelegate.

public class RepositoryListViewController extends UIViewController implements UITableViewDataSource, UITableViewDelegate

3. Next override required method and fulfill them with sample data:

@Override
public UITableViewCell tableViewCellForRowAtIndexPath(UITableView tableView, NSIndexPath indexPath) {
   UITableViewCell cell = (UITableViewCell) tableView.dequeueReusableCellWithIdentifierForIndexPath(CELL_IDENTIFIER, indexPath);
   cell.textLabel().setText("Test");
   return cell;
}

@Override
public long tableViewNumberOfRowsInSection(UITableView tableView, @NInt long section) {
   return 5;
}

4. CELL_IDENTIFIER is constant. It’s value must be the same as cell identifier set in Xcode.

private static final String CELL_IDENTIFIER = "repositoryItemCell";

5. Run app normally, before click green arrow select ios in combobox. The app should look like the screen below:

iPhone 5 simulator showing a Repositories list filled with five sample rows reading Test

Fulfill table view with data from GitHub API

1. To start fetching requests from API, we need to do them in background thread. RxJava doesn’t have access to application’s threads. In Android apps we add rxandroid in build.gradle . For iOS app we use MOE port for RxJava. It is available in the rxmoe repository on GitHub (opens in a new tab). Just add this files to project.

2. Next declare NSOperationQueue in RepositoryListViewController.java

private final NSOperationQueue operationQueue;

3. Set NSOperationQueue in constructor

protected RepositoryListViewController(Pointer peer) {
   super(peer);
   operationQueue = NSOperationQueue.alloc().init();
   operationQueue.setQualityOfService(NSQualityOfService.Background);
}

4. Override method viewDidLoad() . Inside then initialize RepositoryPresenter from common module and start fetching repositories.

@Override
public void viewDidLoad() {
   super.viewDidLoad();
   RepositoryPresenter repositoryPresenter = new RepositoryPresenter(this);
   repositoryPresenter.getRepositories(IOSSchedulers.mainThread(), IOSSchedulers.handlerThread(operationQueue));
}

5. To receive data in View Controller use IRepositoryView interface, add them to implemented interfaces by RepositoryViewController and override method.

@Override
public void showRepositories(List<Repository> allRepositories) {
   this.mAllRepositories = allRepositories;
   repositoryTableView().reloadData();
}

6. Declare mAllRepositories

private List<Repository> mAllRepositories = new ArrayList<>();

7. Change tableViewCellForRowAtIndexPath and tableViewNumberOfRowsInSection to use mAllRepositories

@Override
public UITableViewCell tableViewCellForRowAtIndexPath(UITableView tableView, NSIndexPath indexPath) {
   UITableViewCell cell = (UITableViewCell) tableView.dequeueReusableCellWithIdentifierForIndexPath(CELL_IDENTIFIER, indexPath);
   cell.textLabel().setText(mAllRepositories.get((int) indexPath.row()).getName());
   return cell;
}

@Override
public long tableViewNumberOfRowsInSection(UITableView tableView, @NInt long section) {
   return mAllRepositories.size();
}

8. Run application, if list is empty, it is probably caused by Security Exception. You can find this bug using debugger. You should catch them in onError method in getRepositories in common module. To fix them open project.pbxproj file and checked Copy CA certificates

Multi-OS Engine project settings for MoeSampleApp with Copy CA certificates ticked, the fix for an empty list caused by a security exception

iOS app is finished now.

Create Android views

The last part of our MOE app is to create Android app.

1. Add to build.gradle dependency:

compile 'com.android.support:recyclerview-v7:25.3.0'

2. Next add RecyclerView in activity_repo.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="pl.snowdog.moesampleapp.RepositoryActivity">

   <android.support.v7.widget.RecyclerView android:id="@+id/repository_recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</RelativeLayout>

3. Create row for Recycler View

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" android:orientation="vertical">

   <TextView android:id="@+id/titleTextView" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="18sp"/>
</LinearLayout>

4. Next create simple RepositoriesAdapter

public class RepositoriesAdapter extends RecyclerView.Adapter<RepositoriesAdapter.RepositoriesViewHolder> {

   private List<Repository> mRepositoriesList;

   public RepositoriesAdapter(List<Repository> mRepositoriesList) {
       this.mRepositoriesList = mRepositoriesList;
   }

   @Override
   public RepositoriesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.repo_row, parent, false);
       return new RepositoriesViewHolder(view);
   }

   @Override
   public void onBindViewHolder(RepositoriesViewHolder holder, int position) {
       holder.titleTextView.setText(mRepositoriesList.get(position).getName());
   }

   @Override
   public int getItemCount() {
       return mRepositoriesList.size();
   }

   public class RepositoriesViewHolder extends RecyclerView.ViewHolder {
       public TextView titleTextView;
       public RepositoriesViewHolder(View itemView) {
           super(itemView);
           titleTextView = (TextView) itemView.findViewById(R.id.titleTextView);
       }
   }
}

5. In onCreate method set Recycler View and initialize RepositoryPresenter

mRecyclerView = (RecyclerView) findViewById(R.id.repository_recycler_view);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(),
               LinearLayout.VERTICAL);
mRecyclerView.addItemDecoration(dividerItemDecoration);
RepositoryPresenter repositoryPresenter = new RepositoryPresenter(this);
repositoryPresenter.getRepositories(AndroidSchedulers.mainThread(), Schedulers.newThread());

6. Implement IRepositoryView and override method

 @Override
   public void showRepositories(List<Repository> allRepositories) {
       mAllRepositories = allRepositories;
       mRepositoriesAdapter = new RepositoriesAdapter(mAllRepositories);
       mRecyclerView.setAdapter(mRepositoriesAdapter);
   }

7. In AndroidManifest.xml in activity tag add

android:label="@string/repository_title"

8. Set repository_titile in string.xml

<string name="repository_title">Repositories</string>

Android app is finished.

Summary

As you can see code in app and common modules looks like regular Android Application. It’s Model-View-Presenter where app contains View and common contains Model and Presenter. ios module contains View for iOS and it’s most difficult part for Android Developers. We hope that this tutorial will be comprehensive to start with Multi-OS-Engine.

Other external links

1. Multi-OS Engine home page (opens in a new tab)

2. iOS – OperationQueue documentation (opens in a new tab)

3. iOS – UITableView documentation (opens in a new tab)

Ewelina Bukowska

Questions this post answers

Related reading

No items found.
On this page
Related service
See the service
Where this leads

From store to scalable.
We make Magento perform.

The eCommerce agency behind 100+ high-stakes projects, ready to build yours.

More from Insights