Skip to content
By Mark Biegel

Making iOS & Android apps with Rails & Turbolinks

So you want to build an app? One of the first decisions that needs to be made is one of time and resources. For a business, it is crucial to know your market in order to successfully launch your product. But what about the App market? Which platform you do you choose to launch to first? Apple? Android? Web?

At this point you might have already considered a HTML5 web app so you can be cross platform and launch to everyone. Or perhaps you decided against it, due to poor performance and not having a “native app” feel.

Enter Turbolinks 5 + Ruby on Rails

Turbolinks is a library that is designed to speed up web apps. By doing so, it makes web apps run at fantastic speed on mobile devices essentially overcoming the slow nature of traditional HTML5 web apps.

But the real benefit of building a Turbolinks-enabled web app is that Turbolinks itself comes with iOS and Android native wrappers that give the app a natural feel. Another bonus is being able to submit your app and have it discoverable in the app stores.

How to get started

To get started you will need a few key ingredients:

  • A Ruby on Rails app with Turbolinks enabled.
  • The Turbolinks iOS wrapper.
  • https://github.com/turbolinks/turbolinks-ios
  • Xcode software (Install via App store)
  • The Android iOS wrapper.
  • https://github.com/turbolinks/turbolinks-android
  • Android Studio
  • https://developer.android.com/studio/index.html

Once you have your web app running, you can start on the very quick mobile implementation.

Regardless of which platform you choose to start with (iOS/Android), once you have your project set up, the theory is the same. Your mobile app will consist of three core parts:

  • A Base Controller/Activity.
  • A Turbolinks Navigation Controller/Activity.
  • A Javascript Notification Handler.

The Base Controller/Activity is the entry point to your app. This is where you set up default menus and authentication checks if you require user authentication.

//iOS Base Controller example
class BaseVisitableViewController: Turbolinks.VisitableViewController{
    // Perform any native setup.
    // Turbolinks.VisitableViewController is where the magic happens
}
// Android Base Activity example
public class BaseActivity extends AppCompatActivity {
    TurbolinksView turbolinksView;
    private TurbolinksHelper turbolinksHelper;

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base);

        location = getIntent().hasExtra(INTENT_URL) ? getIntent().getStringExtra(INTENT_URL) : getString(R.string.base_url);
        turbolinksHelper = new TurbolinksHelper(this, this, turbolinksView);
        turbolinksHelper.visit(location + path);
      }
}

The Turbolinks Navigation Controller/Activity is the heart of your native app.

This controls what happens when you click the links in your web app such as:

  • Open a link in an external browser.
  • Push navigation to a new screen.
  • Open the page as a modal.
  • Dismiss a modal.
  • Open the Camera.
  • Show Native alert messages.
// iOS Turbolinks Controller example
class TurbolinksNavigationController: UINavigationController{
  private lazy var session: Session = {
      let session = Session(webViewConfiguration: self.webViewConfiguration)
      session.delegate = self
      return session
  }()

  private lazy var webViewConfiguration: WKWebViewConfiguration = {
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.addScriptMessageHandler(self, name: "YourAppUserAgent")
        return configuration
    }()

    func visit(URL: NSURL) {
      if(URL.path.hasSuffix("/camera")){
        // open a native camera action
        return
      }
      showVisitableViewControllerWithUrl(URL, action: .Advance)
    }

    private func showVisitableViewControllerWithUrl(URL: NSURL, action: Action) {
      let visitable : BaseVisitableViewController = BaseVisitableViewController(URL: finalUrl)
      switch action {
        case .Advance:
            pushViewController(visitable, animated: true)
        case .Replace:
            if viewControllers.count == 1 {
                setViewControllers([visitable], animated: false)
            } else {
                popViewControllerAnimated(false)
                pushViewController(visitable, animated: false)
            }
        }
        session.visit(visitable)
    }
}
// Android Turbolinks Activity example
public class TurbolinksHelper implements JsListener, TurbolinksAdapter {

  public TurbolinksHelper(Context context, Activity activity, TurbolinksView turbolinksView) {
    setupTurbolinks();
  }
  private void setupTurbolinks() {
        TurbolinksSession.getDefault(context).getWebView().getSettings().setJavaScriptEnabled(true);
        TurbolinksSession.getDefault(context).getWebView().addJavascriptInterface(new JsBridge(context, this), "android");
    }
  public void visit(String url, boolean withCachedSnapshot) {
    if (withCachedSnapshot) {
      TurbolinksSession.getDefault(context).activity(activity).adapter(this).restoreWithCachedSnapshot(true).view(turbolinksView).visit(url);
    } else {
      TurbolinksSession.getDefault(context).activity(activity).adapter(this).view(turbolinksView).visit(url);
    }
  }
  // Handle your URL requests
  @Override
    public void visitProposedToLocationWithAction(String location, String action) {
        if (action.equals("replace")) {
            visit(location, true);
        } else if(location.contains("/photo/new")){
            // Open a Camera intent rather than a page
          }else {
            visitToNewActivity(location);
        }
    }
}

Lastly you will need a Javascript Notification Handler.

This will allow your web app to interact with the native side of your iOS/Android app.

When your web app performs actions like navigation and form submission it does so using javascript. Since your web app is running inside an iOS or Android Webview, you can handle the javascript responses and direct the app to perform actions with the returned data.

// iOS Javascript Message Handler Example
extension TurbolinksNavigationController: WKScriptMessageHandler {
    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
      switch message.name {
      case "YourAppUserAgent":
        //handle your JS messages

      default:
            fatalError("Unexpected message")
        }
    }
}
// Android Javascript Message Handler Example

public interface JsListener {
    void loginSuccessful();
    void signoutSuccessful();
}
public class JsBridge {

    private Context context;
    private JsListener listener;

    public JsBridge(@NonNull Context context, @NonNull JsListener listener) {
        this.context = context;
        this.listener = listener;
    }

    @JavascriptInterface
    public void postMessage(String jsonString)  {
        try {
          JSONObject jsonObject = new JSONObject(jsonString);
          // handle your JS message
        }catch (JSONException e){

        }
    }
}

From your rails app you would communicate with the JS message handler like this:

<script>
  TurbolinksNativeMessageHandler.postMessage(JSON.stringify({your_app_user_agent: "Your Message Data"}));
</script>

A note on authentication

It is a good idea to handle the authentication process outside of the Turbolinks flow.

If you want to stay signed into a Turbolinks app then, like most apps, you should use a token-based authentication system, and store the token in the iOS/Android apps local storage.

If you were to simply use Turbolinks to handle the sign-in, you would need to be aware of the state of other Turbolinks views that are already loaded. This is especially relevant if you are using a tab-based menu system as all the tabs are preloaded and therefore would all be set to a sign-in page.

The end result

With these few files in place you can quickly have your web app running with a full native feel on iOS and Android devices.

Not only can this save you countless hours of initial development, but unlike HTML5 web apps, you will be able to submit your native apps to the Apple AppStore and Google Play store.

Into the future

Future development does not need to be limited to Turbolinks at all. You can always take your mobile apps further and start building out the pages as full native components if you wish to do so.

The benefit of the Turbolinks framework works is that you control how the app functions, and if you want a “click” to open a section of your app that used to be web app-based and is now native, then you can simply tell the Turbolinks Navigation controller to do so.

Latest Articles by Our Team

Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.

We Hire Only the Best

reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.

We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.

Free Community Workshops

We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.

  • Webinars

    Webinars

    Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.

    Learn more about webinars

  • Installfest

    Installfest

    The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.

    Learn more about Installfest

  • Development Hub

    Development Hub

    The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.

    Learn more about Development Hub

Get the “reinteractive Review” Monthly Email