Skip to content
PLAY VIDEO PLAY VIDEO PLAY VIDEO
By Jonathan Miles

Fixing Native Datepickers in Turbolinks-Android Apps

Some of our clients make use of Turbolinks-Android to provide a native Android experience for their Rails web apps. This is fine, until you try to use something like an HTML5 date field. When the user taps on the field they get... nothing. No datepicker, not even a keyboard, just a flashing cursor trapped in a forever-empty field.

This is a known issue with Turbolinks-Android. It appears that most HTML5 fields will work, but date/time and autofill will not.

What Turbolinks-Android gives us

One of the nice things Turbolinks-Android provides us is the ability to send messages to the app via javascript.

On the Rails-side, we can just add this snippet: javascript if (this.JsNativeDatePicker) { $(document).on('click', '[type~=date]', function(event) { JsNativeDatePicker.showDatePicker(event.target.id, event.target.value); return false; }); }

For the most part this is fairly clear. We're adding an on-click handler to date fields that fires off the id and value of the field to JsNativeDatePicker.

But what about this JsNativeDatePicker? Where did that come from?

Turbolinks-Android uses a @JavaScriptInterface annotation for public methods that are then magically available for us to call from Javascript. So in our JsNativeDatePicker.java we have something like this:

  public class JsNativeDatePicker
  ...

      @JavascriptInterface
      public void showDatePicker (String id, String value) {
          elementId = id;
          Calendar cal = Calendar.getInstance();
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
          try {
              cal.setTime(sdf.parse(value));
          } catch (ParseException p) {
              cal = null;
          }
          DialogFragment newFragment = new DatePickerFragment(this, cal);
          newFragment.show(activity.getFragmentManager(), "datePicker");
      }

Here we are taking the id and value passed in from the Javascript call and using it to create a new datepicker.

The DatepickerFragment

To display a DatePicker, we are wrapping it in a new DatePickerFragment. The code for this is pretty straight-forward, based on the documentation at https://developer.android.com/guide/topics/ui/controls/pickers

public class DatePickerFragment extends DialogFragment {
    private DatePickerDialog.OnDateSetListener listener;
    private Calendar calendar;

    public DatePickerFragment(DatePickerDialog.OnDateSetListener listener, Calendar calendar) {
        this.listener = listener;
        if (calendar == null) {
            calendar = Calendar.getInstance();
        }
        this.calendar = calendar;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        // Create a new instance of DatePickerDialog and return it
        return new DatePickerDialog(getActivity(), listener, year, month, day);
    }
} 

Fairly self-explanatory. We take an OnDateSetListener to listen for updates when the user selects a date, and a Calendar of the current value to use (defaulting to today via Calendar.getInstance()).

Updating the value

Here the JsNativeDatePicker class is also going to function as our OnDateSetListener to receive updates when the user selects a value.

public class JsNativeDatePicker
  implements DatePickerDialog.OnDateSetListener {

      public void onDateSet(DatePicker view, int year, int month, int day) {
        webView.evaluateJavascript(getUpdateJavascript(elementId, year, month, day),
                new ValueCallback<String>() {
                    @Override
                    public void onReceiveValue(String s) {
                    }
                });
    }

    private String getUpdateJavascript(String id, int year, int month, int day) {
        return String.format(Locale.ENGLISH, "(function() {" +
                    "var input = document.getElementById('%s');" +
                    "input.value = '%d-%02d-%02d';" +
                    "})();",
                id, year, month+1, day);
    }
}

When the user has picked a date, our onDateSet method is hit. Now we have the value, but we still need to update the html field. To do this we inject some javascript straight into the WebView. Note that the month is in the range 0..11 for compatibility with Calendar.MONTH, so we simply +1 it here.

Hooking it up

Now that the pieces are in place we just need to register our shiny new datepicker so it's available from Javascript on the page. When setting up Turbolinks (in my case this was in TurbolinksHelper.java#setupTurbolinks) simply add this line: java // WebView webView = TurbolinksSession.getDefault(context).getWebView(); webView.addJavascriptInterface(new JsNativeDatePicker(activity, webView), "JsNativeDatePicker");

addJavascriptInterface will take any class as a parameter, then any public methods with the @JavaScriptInterface annotation will be available to call. The second parameter ("JsNativeDatePicker" here) is what you will call from javascript - in our case JsNativeDatePicker.<annotated method>. I've used the class name here for clarity, but you can actually use any name you want as long as it is unique.

Summary

Turbolinks native wrappers provide some very handy functionality, and allow you provide some mobile-native elements without having to duplicate business logic in both the native app and the backend - you just have to be aware of the rough edges where you have to provide the Rails-to-native bridging manually.

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

Next Community Events

Find more events

Get the “reinteractive Review” Monthly Email