Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Hybrid Apps with Rails - a Case Study

Building Hybrid Apps with Rails - a Case Study

How do you launch both an Android and iOS app in a matter of weeks and still gain some of the benefits of going native?

I was tasked with building and launching two mobile versions for our iOS project - dash (usedashnow.com). Initially we started out building a native ios app, but when soon discovered most of our initial users were on android. Instead of rebuilding out a complete native version for android, we decided to adopt a hybrid approach and take advantage of Turbolinks 5 native wrappers.

Some Takeaways

• The benefits of going native, but why it might be best to reconsider a hybrid approach.

• Learn how to integrate turbolinks native wrappers with Android and some pitfalls to avoid when building your native controls.

• And finally, some things to watch out for when integrating turbolinks 5 into your rails app.

Audience

If your rails developer thinking of dipping your toe into mobile development but aren't sure where to start, this talk will be geared to you.

Bio

Hi, I'm Thomas McGoey-Smith (@tamcgoey) a 4th year student at the University of Calgary. Over the last year I took part in an iOS class in hopes to gain a feel for what it was like to build iOS apps.

In March we changed directions and started working on a brand new project called dash - a simply way for students to run errands for each other.

We originally built a full native app but soon switched directions when our first set of users were Android only. Instead of building out a completely new app we decided to take another look at building a hybrid solution so we could easily ship both an Android and iOS version right away.

I'll be sharing the lessons learned along the way in hopes that you might reconsider a hybrid approach for your mobile development.

Visit the talk at: https://thomas.codes/talks

Thomas McGoey-Smith

May 03, 2016
Tweet

Other Decks in Technology

Transcript

  1. +

  2. Our Users @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_dashes);


    
 // Setup Native Toolbar
 mToolbar = (Toolbar) findViewById(R.id.dashes_toolbar);
 mToolbar.setTitle(R.string.title_activity_dashes);
 
 setSupportActionBar(mToolbar); 
 
 // Find the custom TurbolinksView object in your layout
 turbolinksView = (TurbolinksView) findViewById(R.id.turbolinks_dashes_view);
 turbolinksView.setBackgroundColor(Color.rgb(238, 238, 238));
 
 TurbolinksSession.getDefault(this).addJavascriptInterface( new DashWebService(this), “Android”); // Use passed in intentURL else, just stick with the base url...
 location = getIntent().getStringExtra(INTENT_URL) != null ? getIntent().getStringExtra(INTENT_URL) : (getResources().getString(R.string.base_url) + “/dashers/dashes”); dashID = getIntent().getStringExtra(DASH_ID);
 resourceID = getIntent().getStringExtra(RESOURCE_ID);
 
 // Execute the visit
 TurbolinksSession.getDefault(this)
 .activity(this)
 .adapter(this)
 .view(turbolinksView)
 .visit(location);
 
 // Make app active (used for push notifications)
 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 sharedPreferences.edit().putBoolean(IS_APP_ACTIVE, true).apply(); }
  3. Our Users @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_dashes);


    // Setup Native Toolbar mToolbar = (Toolbar) findViewById(R.id.dashes_toolbar);
 mToolbar.setTitle(R.string.title_activity_dashes);
 
 setSupportActionBar(mToolbar);
 
 … }
  4. Our Users @Override
 protected void onCreate(Bundle savedInstanceState) {
 // Find

    the custom TurbolinksView object in your layout turbolinksView = (TurbolinksView) findViewById(R.id.turbolinks_dashes_view);
 turbolinksView.setBackgroundColor(Color.rgb(238,238,238));
 
 TurbolinksSession.getDefault(this). addJavascriptInterface(new DashWebService(this), “Android”); … }
  5. Our Users @Override
 protected void onCreate(Bundle savedInstanceState) {
 // Use

    passed in intentURL else, just stick with the base url...
 location = getIntent().getStringExtra(INTENT_URL) != null ? getIntent().getStringExtra(INTENT_URL) : (getResources().getString(R.string.base_url) + “/dashers/dashes”); // Execute the visit
 TurbolinksSession.getDefault(this)
 .activity(this)
 .adapter(this)
 .view(turbolinksView)
 .visit(location);
 … }
  6. Our Users <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 >
 


    <!-- "Mark Favourite", should appear as action button if possible -->
 <group android:id="@+id/available_dashes_group">
 <item
 android:id="@+id/action_dashes_profile"
 android:icon="@drawable/ic_account_circle_24dp"
 android:title="@string/menu_dashes_profile"
 app:showAsAction="always"/>
 
 <item
 android:id="@+id/action_dashes_help"
 android:title="@string/menu_dashes_help"
 app:showAsAction="never"/>
 
 </group>
 </menu>
  7. Our Users @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 super.onCreateOptionsMenu(menu);
 


    MenuInflater inflater = getMenuInflater();
 inflater.inflate(R.menu.menu_dashes, menu);
 
 this.mMenu = menu;
 
 return true;
 }
  8. Our Users @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle

    item selection
 switch (item.getItemId()) {
 case R.id.action_dashes_profile:
 Intent intent = new Intent(this, ProfileActivity.class);
 intent.putExtra( INTENT_URL, (getResources().getString(R.string.base_url) + “/dashers/ profile”) );
 this.startActivity(intent);
 return true; … }
  9. <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_height="match_parent"
 android:layout_width="match_parent"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:orientation="vertical">
 <android.support.v7.widget.Toolbar


    android:id="@+id/dashes_toolbar"
 android:layout_width="match_parent"
 android:layout_height="?attr/actionBarSize"
 android:background="?attr/colorPrimary"
 android:elevation="4dp"
 android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
 app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
 <com.basecamp.turbolinks.TurbolinksView
 android:id="@+id/turbolinks_dashes_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>
 </LinearLayout>
  10. @Override
 public void visitProposedToLocationWithAction(String location, String action) { 
 Intent

    intent = null;
 
 if (location.contains("file_report")) {
 intent = new Intent(this, ActiveDashReportActivity.class);
 intent.putExtra(INTENT_URL, location);
 intent.putExtra(DASH_ID, dashID);
 intent.putExtra(RESOURCE_ID, resourceID);
 
 this.startActivity(intent); } else if (location.contains("sign_in")) { intent = new Intent(this, LoginActivity.class);
 
 // pass in the same intent data as above this.startActivity(intent);
 this.finish();
 } … }
  11. @Override
 public void requestFailedWithStatusCode(int statusCode) {
 if (statusCode == 404)

    {
 TurbolinksSession.getDefault(this) .activity(this)
 .adapter(this)
 .restoreWithCachedSnapshot(false)
 .view(turbolinksView) .visit(getResources().getString( R.string.base_url) + "/dashers/error");
 } 
 }
  12. <div class="row"> <div class="col s12"> <%= form_for(@dasher, url: dashers_profile_path, method:

    :post, remote: true) do |f| %> <div class="row"> <div class="input-field col s12 mtm"> <%= f.label :first_name, "First Name", class: "active" %> <%= f.text_field :first_name %> </div> … </div> </div> <% end %> </div> </div>
  13. <% if @errors %> Android.showToast("<%= @errors %>"); Turbolinks.visit('<%= dashers_profile_path %>');

    <% else %> Android.showToast("Update Profile Successfully!"); Turbolinks.visit('<%= dashers_dashes_path %>'); <% end %>
  14. class DasherSessionsController < Devise::SessionsController layout "dashers" respond_to :js, :html #

    without it neither on success create.js.erb runs # We need to overrite devises redirect to so we can # actually use it on our web app def create self.resource = warden.authenticate!(auth_options) set_flash_message!(:notice, :signed_in) sign_in(resource_name, resource) yield resource if block_given? set_redirect_to @dasher_token = current_dasher.access_token respond_with resource, location: after_sign_in_path_for(resource) end … end
  15. <% if @redirect_to.nil? %> window.location.href = "<%= Rails.configuration.app_uri %>"; <%

    else %> Turbolinks.visit("<%= @redirect_to %>"); <% end %> Android.showToast("You've been successfully signed in."); Android.enablePushNotifications("<%= @dasher_token %>");
  16. Credits - Androids: etnyk — https://www.flickr.com/photos/etnyk/5588953445/ - Switch: kohtzy -

    https://www.flickr.com/photos/kohtz/2791564888/sizes/l - Old Apple Computer: jimabeles - https://www.flickr.com/photos/abeles/ 21520805945/sizes/l