Category: Mobile

Add a New Field to a Realm JS Database using Migrations

Add a New Field to a Realm JS Database using Migrations

While building my app ThinkBack I decided to use Realm as my underlying data-store. ThinkBack helps users commit notes or ideas to long term memory through passive, daily recall. To support changes and new features in the app I need to modify the existing Realm JS object model that defines the schema.

Modify Schema and Perform Migration of Existing Database

The original Note schema and Realm are defined as follows:

const noteSchema: Realm.ObjectSchema = {
  name: "Note",
  properties: {
    id: "string",
    content: "string",
    createdOn: "date",
    modifiedOn: "date",
    ...
  },
  primaryKey: "id",
}

const realm = new Realm({ path: "default", schema: [ noteSchema
... ], schemaVersion: 0, })

I wanted to modify this schema to introduce a new field called “title”. Moving forward a note must have a title and, if one isn’t provided, a default title “No title” will be applied. Adding a new field to a database is something Realm JS can handle automatically for you but applying a default value requires a little manual intervention. In order to apply a default value for existing records in the database I leveraged Realm Migrations.

Here’s what the new schema and Realm looks like:

const noteSchema: Realm.ObjectSchema = {
  name: "Note",
  properties: {
    id: "string",
title: "string", content: "string", createdOn: "date", modifiedOn: "date", ... }, primaryKey: "id", }

const realm = new Realm({ path: "default", schema: [ noteSchema ... ], onMigration: (oldRealm: Realm, newRealm: Realm) => {
const newNotes = newRealm.objects<Note>("Note"); for (let i = 0; i < newNotes.length; i++) { newNotes[i].title = "No title"; } }, schemaVersion: 1, })

According to the Mongo DB Realm docs when you create a Configuration with a schema version greater than the realm’s current version, Realm runs a migration function that you define. The function has access to the realm’s version number and incrementally updates objects in the realm to conform to the new schema. From the examples above the original schema version was 0 (default), and in the updated schema the version is 1. When Realm detects a newer version of an existing database the onMigration function defined above will be invoked, allowing opportunity to apply a default title to each note in the Realm.

How to Paginate Query Results in React Native with Realm JS (MongoDB)

How to Paginate Query Results in React Native with Realm JS (MongoDB)

While building my app ThinkBack I decided to use Realm as my underlying data-store. ThinkBack’s home screen contains a list of notes which need pagination to avoid loading everything unnecessarily.

From the Realm docs:

Pagination & Limits

Some queries only need to access a subset of all objects that match the query. Realm’s lazy-loaded collections only fetch objects when you actually access them, so you do not need any special mechanism to limit query results.

For example, if you only want to find 10 matching objects at a time (such as in a paged product catalog) you can just access ten elements of the results collection. To advance to the next page, access the next ten elements of the results collection starting at the index immediately following the last element of the previous page.

Lazy-loading removes the need to build paging logic into the query itself and instead defers pagination or limiting results to when you interact with the result set. In other words, paging is handled at the time you want to access/materialize the objects into memory rather than the time you issue a query to Realm.

This post aims to demonstrate this concept through a simplified implementation.

End to End Paging Implementation

The following implementation will load a series of “notes” from Realm, apply pagination logic and present the results in a FlatList to the user. I tend to prefer a layer of abstraction between the UI and the underlying data store, this keeps a clean separation between the front-end and any business logic that may exist.

To support this we’ll create the following:

  • RealmStore.ts, which creates an instance of the Realm datastore.
  • Note.ts, which defines the schema of the Note table in Realm.
  • NoteService.ts, which will contain logic for querying Realm and paginating the results.
  • App.tsx, which will store paging “state” (i.e. what page we’re on, page size), interact with the NoteService class to load paged notes and present them in a FlatList.

The structure of these files in the project is as follows:

root/
├─ realm/
│ ├─ Note.ts
│ ├─ NoteService.ts
│ ├─ RealmStore.ts
├─ App.tsx

To follow along locally, initialize a new React native app and install Realm JS.

Note.ts

This class defines the schema of a Realm object and will ultimately map to a table in Realm.

import uuid from 'react-native-uuid';
import Realm from 'realm';

export interface NoteProps {
  id?: string;
  content: string;
  createdOn?: Date;
  modifiedOn?: Date;
}

export default class Note {
  public id: string;
  public content: string;
  public createdOn?: Date;
  public modifiedOn?: Date;

  constructor({
    id = uuid.v4().toString(),
    content,
    createdOn,
    modifiedOn,
  }: NoteProps) {
    const now = new Date();

    this.id = id;
    this.content = content;
    this.createdOn = createdOn ?? now;
    this.modifiedOn = modifiedOn ?? now;
  }

  static schema: Realm.ObjectSchema = {
    name: 'Note',
    properties: {
      id: 'string',
      content: 'string',
      createdOn: 'date',
      modifiedOn: 'date',
    },
    primaryKey: 'id',
  };
}

RealmStore.ts

This class creates a new instance of the Realm which we’ll use in NoteService.ts to interact with the datastore.

import Note from './Note';
import {Realm} from '@realm/react';

export default new Realm({
  path: 'default',
  schema: [Note.schema],
  deleteRealmIfMigrationNeeded: __DEV__,
});

NoteService.ts

This class is responsible for loading data from the datastore and returning a paged result based on the paging properties.

Line 20 obtains a reference to the Notes Realm object but doesn’t actually issue any queries yet thanks to lazy loading which defers queries to only when you need them (accessing an object). This is effectively saying “give me access to all Notes but don’t load them into memory yet until I figure out which ones I want”.

Line 25 calls getPagedCollection which uses the provided paging options (e.g. page size, current page) to access a subset of the Realm Notes. Calling slice on the Realm Notes array is a form of access as we’re now directly interacting with individual notes. This issues a query against the Realm store and loads the subset of notes being accessed. For example, if 100 notes existed in array then array.slice(0, 10) would cause the first 10 notes to be loaded.

import {RealmObject} from 'realm/dist/public-types/Object';
import Note from './Note';
import realm from './RealmStore';

export interface PagingOptions {
  page: number;
  pageSize: number;
  hasNext: boolean;
}

export interface PagedCollection {
  result: Array;
  pagingOptions: PagingOptions;
}

export class NoteService {
  getPagedNotes(pagingOptions: PagingOptions): PagedCollection {
    // Lazy load the notes. These aren't materialized yet so no paging needs to occur
    // at the "realm" level.
    let notes = realm.objects('Note');
    notes = notes.sorted([['createdOn', false]]);

    // Access a subset of the notes based on the paging options (e.g. page, page size). This will
    // cause actual loading/materialization of the matching object(s) from the datastore.
    const pagedResult = this.getPagedCollection(notes, pagingOptions);
    console.log(
      `Paged Notes Count: ${
        pagedResult.result.length
      }, Paging options: ${JSON.stringify(pagedResult.pagingOptions)}`,
    );

    return pagedResult;
  }

  getPagedCollection(
    array: Realm.Results<RealmObject<T, never> & T>,
    pagingOptions: PagingOptions,
  ): PagedCollection {
    const {pageSize} = pagingOptions;
    let currentPage = pagingOptions.page;
    const totalPages = Math.ceil(array.length / pageSize);

    if (currentPage < 1) {
      currentPage = 1;
    }

    if (currentPage > totalPages) {
      currentPage = totalPages;
    }

    const start = (currentPage - 1) * pageSize;
    let end = start + pageSize;
    if (end > array.length) {
      end = array.length;
    }

    return {
      result: array.slice(start, end),
      pagingOptions: {
        ...pagingOptions,
        hasNext: end !== array.length,
      },
    };
  }
}

// singleton instance for convenience
export const noteService = new NoteService();

App.tsx

This component is responsible for maintaining paging state as the user interacts with the list. As the user scrolls and approaches the end of the list the next page is loaded.

Line 51 leverages the FlatList onEndReached callback which fires as the list breaches the threshold set by onEndReachedThreshold. The handler for this callback increments the current page state by one.

Line 35 of the useEffect hook declares pagingOptions.page as a dependency which means the function will be invoked any time the “page” value is modified.

Line 27 passes the current paging options state to the noteService.getPagedNotes function which returns a paged collection of notes from Realm. The loaded notes are then merged with any existing notes state which causes the component to rerender and the latest set of notes to be shown.

import React, {useState} from 'react';
import {FlatList, SafeAreaView, StatusBar, Text} from 'react-native';
import Note from './realm/Note';
import {noteService} from './realm/NoteService';

const itemStyle = {
  padding: 20,
  margin: 5,
  borderWidth: 1,
  borderColor: 'red',
};

function App(): React.JSX.Element {
  const [pagingOptions, setPagingOptions] = useState({
    page: 1,
    pageSize: 10,
    hasNext: true,
  });
  const [notes, setNotes] = useState<Note[]>([]);

  React.useEffect(() => {
    if (!pagingOptions.hasNext) {
      // no more pages remain so don't attempt to load anything.
      return;
    }

    const pagedResult = noteService.getPagedNotes(pagingOptions);
    // merge the new notes with the existing note state.
    const newNotes = [...notes, ...pagedResult.result];
    console.log(`Total notes loaded: ${newNotes.length}`);
    setPagingOptions(pagedResult.pagingOptions);
    setNotes(newNotes);

    return () => {};
  }, [pagingOptions.page]);

  return (
    <SafeAreaView>
      <StatusBar />
      <FlatList
        contentInsetAdjustmentBehavior="automatic"
        data={notes}
        extraData={notes.length}
        renderItem={item => {
          return (
            <Text key={item.item.id} style={itemStyle}>
              {item.item.content}
            </Text>
          );
        }}
        onEndReached={() => {
          if (pagingOptions.hasNext) {
            // breached the "end reached" threshold in the list and there's more notes to load,
            // increment the current page by one.
            setPagingOptions({
              ...pagingOptions,
              page: pagingOptions.page + 1,
            });
          }
        }}
        onEndReachedThreshold={0.5}
      />
    </SafeAreaView>
  );
}

export default App;

Additional Content

For convenience sake I used the below createNotes helper function to generate some notes for testing purposes in the Realm database. You can add a log statement to see the absolute path where your Realm database lives:

console.log(`Realm path: ${realm.path}`);
The following function was added to the NoteService.
createNotes() {
  for (let i = 0; i < 30; i++) {
    const createdOn = new Date();
    const note: Note = {
      id: `${i}`,
      content: `[${
        i + 1
      }] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Habitant morbi tristique senectus et netus et malesuada fames. Etiam non quam lacus suspendisse faucibus interdum. Faucibus et molestie ac feugiat sed. Feugiat in ante metus dictum at. Morbi blandit cursus risus at ultrices mi tempus.`,
      createdOn,
      modifiedOn: createdOn,
    };
    realm.write(() => {
      realm.create<Note>('Note', note);
    });
  }
}

I added a button above the FlatList to invoke the helper function and generate the notes:

<Button
  title="Create sample notes"
  onPress={() => noteService.createNotes()}
/>

Xamarin iOS Detect if Ringer Is On Silent or Muted

Xamarin iOS Detect if Ringer Is On Silent or Muted

There’s currently no native support for detecting whether or not the device ringer is set to silent. This poses an issue when you want to leverage the state of the ringer to trigger some action within the app, such as notifying the user.

Fortunately there’s a fairly straightforward way to detect this on your own using a system sound.

Detecting Ringer State by Playing a System Sound

When the ringer is on silent it prevents any system sounds you attempt to play from actually playing. Conversely when the ringer is not on silent the sound will play as expected. We can leverage this behavior to confidently determine whether or not the device is on silent based on how quickly our system sound finishes playing.

Add Muted Sound File to Resources

The first thing we need to do is obtain a copy of a sound file that doesn’t actually make any noise. Here’s one for convenience. After downloading the file add it to the Resources folder in your iOS project.

I added mine under Resources > Audio:

Audio Resources Folder

Write Code to Determine Playback Time of System Sound

Now that we have the file in place we need to create a System Sound and determine how fast the sound played.

The SystemSound object has an observer method called AddSystemSoundCompletion, which as the name suggests lets us know when the sound finishes playing. The idea here is simple: find the time delta between when the sound started playing and when it finished. If the difference in time is negligible than we can safely assume the ringer is on silent. However, if the time delta is closer to how long the sound file would typically take to play, it’s likely the ringer is not on silent.

The following method can be added to your view controller or other class that needs to detect the state of the ringer and take action:

public void IsMuted(Action mutedCheckComplete) {
    // create an instance of the SystemSound object, pointing to your "mute" sound resource
    var soundFilePath = NSBundle.MainBundle.PathForResource("Audio/mute", "caf");
    var mutedSound = new SystemSound(new NSUrl(soundFilePath, false));

    // capture the start time of the sound
    DateTime startTime = DateTime.Now;

    mutedSound.AddSystemSoundCompletion(() => {
        // find the delta between start and end times to determine if the sound played or was cut short.
        var endTime = DateTime.Now;
        var timeDelta = (endTime - startTime).Milliseconds;
        var muted = timeDelta < 100;

        // perform the callback to the invoker of this method, letting them know we have an answer.
        mutedCheckComplete(muted);
    });

    mutedSound.PlaySystemSound();
}

In my tests the time delta was very close to zero when the ringer was on silent, and closer to ~300ms when not on silent. In the above snippet I consider anything less than 100ms to be considered muted.

Now that the method is created we can invoke it whenever we need to check the status of the ringer. In my case, I use this in the ViewWillAppear method of my view controller. To avoid any unexpected lag between loading the UI and performing this check, I added the method call to the dispatch queue as follows:

DispatchQueue.MainQueue.DispatchAfter(new DispatchTime(DispatchTime.Now, TimeSpan.FromMilliseconds(100)), () => {
    IsMuted((muted) => {
        Debug.WriteLine($"Is Muted: {muted}");
    });
});

There we have it, a fairly trivial approach at determining the state of the ringer.

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

In my Xamarin iOS application I’m leveraging the UITableView control to page through a collection of items in a performant manner. The UITableView uses the GetCell method of the UITableViewDataSource delegate to create or reuse instances of cells as they become visible on screen.

Great, that sounds efficient, and it is! However, in my application the GetCell method was being called once for every cell when the UITableView was initialized, including the invisible ones.

What gives?

This post spawned from a stackoverflow question I posted, then immediately found the answer to.

Replicating the Behavior By Example

The UITableView in question was added to a ViewController in the StoryBoard. Within the ViewController’s ViewDidLoad method, I have the following setup:

public override void ViewDidLoad() {
    base.ViewDidLoad();

    var model = new UIColor[] {
        UIColor.Green,
        UIColor.Red,
        UIColor.Magenta,
        UIColor.Cyan,
        UIColor.Blue,
        UIColor.Purple
    };

    SampleTableView.RowHeight = SampleTableView.Frame.Height;
    SampleTableView.Setup(model);
}

In short, a model consisting of a collection of colors is created and passed through to a Setup method on my custom UITableView, SampleTableView.
In addition to the setup, I already know the height of each row ahead of time, so the RowHeight property of the UITableView is set. As you can see, each row will take the full height of the UITableView. Swiping up or down will then reveal the next page.

Here’s what the SampleTableView and related dependencies look like:

public class SampleCell : UITableViewCell {
    public void Setup(UIColor color) {
        BackgroundColor = color;
    }
}

public class SampleTableViewDataSourceDelegate : UITableViewDataSource {
    public SampleTableViewDataSourceDelegate(UIColor[] model) {
        this.model = model;
    }

    private readonly UIColor[] model;

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
        Debug.WriteLine($"GetCell called. Row {indexPath.Row}");
        var reuseIdentifier = "SampleCell";

        var cell = tableView.DequeueReusableCell(reuseIdentifier) as SampleCell;

        cell = (cell ?? (cell = new SampleCell()));

        cell.Setup(model[indexPath.Row]);

        return cell;
    }

    public override nint NumberOfSections(UITableView tableView) {
        return 1;
    }

    public override nint RowsInSection(UITableView tableView, nint section) {
        return model.Length;
    }
}

public partial class SampleTableView : UITableView {
    public SampleTableView(IntPtr handle) : base(handle) {
        PagingEnabled = true;
    }

    private UIColor[] model;

    public void Setup(UIColor[] model) {
        this.model = model;

        DataSource = new SampleTableViewDataSourceDelegate(model);
    }
}

SampleCell
A custom cell that simply sets the background color to the one provided by the model.

SampleTableViewDataSourceDelegate
A derived class of UITableViewDataSource, which handles implementing the GetCell method. This class is responsible creating and reusing cells as they become visible.

SampleTableView
A derived class of UITableView, which handles initial setup of the UITableView via the Setup method, invoked by the containing ViewController.

We now have a fairly slimmed down example of a UITableView implementation. In the GetCell delegate method there’s a line of code that outputs a message to the log when it’s called. After running the application with Visual Studio in a simulator, the Visual Studio Output console shows the following:

[0:] GetCell called. Row 0
[0:] GetCell called. Row 1
[0:] GetCell called. Row 2
[0:] GetCell called. Row 3
[0:] GetCell called. Row 4
[0:] GetCell called. Row 5

Therein lies the issue; the GetCell method is called once for each cell in our model. Why? Didn’t I specify the RowHeight to be the height of the UITableView? If that’s true, and we know that GetCell should only be called when cells become visible, then why is it being called Model.Length number of times?

UITableView EstimatedRowHeight

Our RowHeight property is set to take up the entire UITableView. When the UITableView data source is initialized, however, the UITableView attempts to estimate the required row height for us on-the-fly via the EstimatedRowHeight property. The EstimatedRowHeight property was not set, effectively relying on the table view to calculate it for us. The solution to this particular problem then is to set the EstimateRowHeight property in addition to the RowHeight property.

Back in the ViewDidLoad method of the ViewController, adding the following line of code above the RowHeight setter did the trick:

 SampleTableView.EstimatedRowHeight = SampleTableView.Frame.Height; 

The Output log now shows what we’d expect, a single call to GetCell on initialize:

[0:] GetCell called. Row 0

Xamarin iOS MT1007 Failed to Launch the Application

Xamarin iOS MT1007 Failed to Launch the Application

After going through the free provisioning profile article I’ve been able to debug my Xamarin iOS app on a physical device. I wanted to verify what happens when the application gets installed for the first time, so I deleted the app from my phone. After running the Visual Studio debugger, the application was reinstalled successfully on my device, but failed to launch the debugger with the following message in the output log:

The app could not be launched on ‘iPhone’. Error: error MT1007: Failed to launch the application ‘/path/to/my/iOS.app’ on the device ‘iPhone’: Failed to launch the application ‘My.App.Namespace’ on the device ‘iPhone’: ESecurity. You can still launch the application manually by tapping on it.

Resolve the Xamarin iOS MT1007 Failed to Launch Error

The message sent to the Visual Studio output log was fairly vague. A quick Google search brought me to several different posts suggesting things like restarting my phone, updating Xcode and so on. Restarting wasn’t the solution and Xcode was already up-to-date. 

Enabling the Device Log prior to debugging gave me a bit more insight: after the error was raised, the following was output to the device log:

The request was denied by service delegate (SBMainWorkspace) for reason: Security (“Unable to launch my.application.namespace because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user”).

The line “its profile has not been explicitly trusted by the user” caught my interest, and made me believe something relate to my provisioning profile was undone. I plugged my phone into the Mac and opened my provisioning profile Xcode project. I then selected my device as the target and ran the application, which greeted me with an error message stating that my device does not trust the developer profile associated with the app.

You Just Need a Little Trust

While the Xamarin iOS MT1007 error can mean many things, in this case it boils down to trust. The solution to this trust issue is the following:

  1. On your iPhone, navigate to Settings -> General -> Device Management.
  2. Within Device Management, you should see your developer provisioning profile, click it.
  3. Trust the developer profile.

After trusting the profile you’ll see a callout stating:

Apps from developer “Your Profile” are trusted on this iPhone and will be trusted until all apps from the developer are deleted.”

Those last few words sum it all up. The problem was introduced the moment I uninstalled the app from my device, and rightfully so according to the relationship my device has with the developer profile.

Setting Opacity of Android Toolbar Overlay Based on ViewPager Position

Setting Opacity of Android Toolbar Overlay Based on ViewPager Position

I’ve recently delved into the world of mobile development using Xamarin with Visual Studio. A tutorial on their website introduced me to the Android toolbar, and how it can be used as a more robust and flexible action bar. This was fairly straightforward to implement but I had a requirement that the toolbar overlay shouldn’t be visible on certain fragments of a ViewPager.

The Goal

What were trying to achieve here is a way to change the opacity of the toolbar overlay based on the ViewPager’s current position and offset. As paging occurs, the toolbar should become more or less transparent depending on the fragment we’re paging to. Here’s an example of what I mean:

Android Toolbar Overlay with ViewPager

The above is what we’ll be covering in this post. Here’s some additional resources that helped get me to this point. These go a bit more in depth in their respective areas and will prove useful in case anything here isn’t quite detailed enough:

The full solution has been added to my GitHub repository as well. Get to forking!

Let’s get to it!

Prerequisites

Before getting started you’ll need to create a new Xamarin Android project in Visual Studio. You’ll also need to add the v13 and v4 Android Support libraries as references.

While this post is targeted toward Xamarin for Visual Studio, the majority of its content is very translatable to Xamarin and Android Studio

In Visual Studio, adding the support libraries can be done via the NuGet Package Manager:

  1. Navigate to Tools -> NuGet Package Manager -> Manage NuGet Packages for this Solution...
  2. Click the Browse window, then search for Xamarin.Android.Support.v13
  3. In the resulting window, install both Xamarin.Android.Support.v13 and Xamarin.Android.Support.v4 libraries

Android Support Libraries NuGet Package Manager

Adding the ViewPager and Fragment Layouts

We’ll start by creating the ViewPager and its accompanying fragments which will enable that smooth swiping transition between the fragment views. This requires that we create a couple new fragments to be used by the ViewPager control, and add the ViewPager itself to the Main.axml layout.

Under the Resources -> layout folder of your project let’s modify Main.axml and add a couple new layouts:

Modify the Main Layout

This is the starting view of our activity and will encompass our toolbar overlay and the ViewPager. For now we’re just going to focus on adding the ViewPager. We’ll circle back to adding the toolbar overlay to this layout once we’ve built it.

Double-click the Main.axml layout file to edit it. The layout will likely open in the visual designer so you’ll want to click the Source button at the bottom left of the designer, then paste the following:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <android.support.v4.view.ViewPager
      android:id="@+id/viewPager"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" />
</RelativeLayout>

Add the FragmentOne Layout

This layout will be the one where we don’t want the toolbar overlay to be visible, allowing the view itself to utilize the screen in its entirety. An example of where this might be handy is when showing a live camera stream in the view; you wouldn’t want the toolbar overlay to take precious real-estate.

Add a new layout file called FragmentOne under the Resources -> layout folder with the following inside:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="#CCC">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="This view should HIDE the toolbar."
      android:textColor="#000"
      android:textSize="14dp"
      android:layout_centerHorizontal="true" />
</RelativeLayout>

Add the FragmentTwo Layout

This layout will embrace the toolbar overlay. Since the toolbar will indeed be an overlay (that is, it’s floating on top of other content, rather than pushing other content below), we’ll need to force the view’s content to begin below the toolbar. This can be achieved in a number of ways, but for this example we’re just adding padding to the top of the fragment.

Add a new layout file called FragmentTwo under the Resources -> layout folder with the following inside:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#FFF"
  android:paddingTop="?android:attr/actionBarSize">
  <TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="This view should SHOW the toolbar."
    android:textColor="#000"
    android:textSize="14dp"
    android:padding="5dp"
    android:layout_centerHorizontal="true" />
</LinearLayout>

Implement the FragmentPagerAdapter for the ViewPager

At this point we have the fragment layouts for our ViewPager in place and the ViewPager itself added to the main layout. Now we need to implement a FragmentPagerAdapter for our ViewPager so it knows about our fragments, and how to view them.

Create the ViewPageFragmentAdapter Class

As I mentioned, the ViewPager control requires a concrete implementation of the FragmentPagerAdapter so it knows how to handle paging. In your project, add a new class called ViewPageFragmentAdapter with the following:

 public class ViewPageFragmentAdapter : FragmentPagerAdapter {
    public ViewPageFragmentAdapter(FragmentManager fragmentManager, List<Fragment> fragments) : base(fragmentManager) {
        this.fragments = fragments;
    }

    private readonly List<Fragment> fragments;

    public override int Count => fragments.Count;

    public override Fragment GetItem(int position) {
        if (position < 0) {
            position = 0;
        }

        if (position >= Count) {
            position = Count - 1;
        }

        return fragments[position];
    }
 }

To resolve the missing reference error you’re seeing on the FragmentPagerAdapter, add the using statement using Android.Support.Android.V13.App;.

Modify the MainActivity Class

Our MainActivity class needs to be updated to do the following:

  • Grab a reference to our ViewPager
  • Implement the ViewPager.IOnPageChangeListener interface, which is responsible for handling ViewPager paging and will eventually be where we set the opacity of our toolbar overlay
  • Set the ViewPageFragmentAdapter as the official adapter of our ViewPager.

Modify your MainActivity class to look as follows:

[Activity(Label = "ToolbarOverlay", MainLauncher = true)]
public class MainActivity : Activity, ViewPager.IOnPageChangeListener {
    public void OnPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    public void OnPageScrollStateChanged(int state) {
    }

    public void OnPageSelected(int position) {
    }

    private ViewPager viewPager;
    protected override void OnCreate(Bundle savedInstanceState) {
        base.OnCreate(savedInstanceState);

        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.Main);

        var fragments = new List<Fragment> {
            new FragmentOne(),
            new FragmentTwo(),
        };

        viewPager = FindViewById<ViewPager>(Resource.Id.viewPager);
        viewPager.Adapter = new ViewPageFragmentAdapter(FragmentManager, fragments);
        viewPager.AddOnPageChangeListener(this);
    }

}

To resolve the reference error you’re seeing on ViewPager.IOnPageChangeListener, add the using statement using Android.Support.V4.View.

At this point we’ve created a ViewPager, a couple fragments for it to page through and implemented the necessary code to teach the pager how to page. With all that in place we should have a fully functional pager, but the default action bar is still visible. Let’s replace it with our custom toolbar overlay!

Replacing the Default Action Bar with a Toolbar Overlay

I mentioned it before, but this guide on Xamarin’s website helped walk me through how to replace the action bar with a custom toolbar. We’ll more or less be following its implementation with a few adjustments.

Create a Custom Theme to Disable the Action Bar

Before we can use our custom toolbar overlay, we need to disable the default action bar. Disabling the action bar can be done by implementing a custom theme. In your project under the Resources -> values folder, add a new XML file called styles.xml with the following:

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <style name="StoriKnowTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowActionBar">false</item>
    <item name="android:colorPrimary">#5A8622</item>
  </style>
</resources>

That wasn’t too bad, but we still need to set this as the theme in the the Android App Manifest file before our application starts using it. In your project, open the AndroidManifest.xml file under the Properties tree and add the android:theme="@style/StoriKnowTheme" attribute to the application node. Here’s what mine looks like:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
          package="ToolbarOverlay.ToolbarOverlay" 
          android:versionCode="1" 
          android:versionName="1.0">
  <uses-sdk android:minSdkVersion="21" />
  <application android:theme="@style/StoriKnowTheme" android:allowBackup="true" android:label="@string/app_name">
  </application>
</manifest>

Create the Page Aware Custom Toolbar Overlay

We’re almost there! At this point we just need to:

  • Create the custom toolbar overlay layout
  • Add this layout to the Main.axml file
  • Set the opacity as we swipe between pages

Under the Resources -> layout folder, add a new layout called Toolbar with the following:

<?xml version="1.0" encoding="utf-8"?>
<Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/actionBarSize"/>

Now open up the Main layout under Resources -> layout and include the toolbar overlay you just created (that’s line 11 below):

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <android.support.v4.view.ViewPager
      android:id="@+id/viewPager"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" />
  <include android:id="@+id/toolbar" layout="@layout/Toolbar" />
</RelativeLayout>

Great, Main.axml knows about the toolbar now, but we still need to tell the main activity that this is actually the new action bar. Along with that, we want to set the opacity of the toolbar overlay as we change pages.

Use the Toolbar as an Action Bar and Set its Opacity During Paging

To wrap things up, let’s modify the MainActivity one last time using the highlighted rows as a visual guide to what changed:

[Activity(Label = "ToolbarOverlay", MainLauncher = true)]
public class MainActivity : Activity, ViewPager.IOnPageChangeListener {
    private ViewPager viewPager;
    private Toolbar toolbar;

    public void OnPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        var opacity = 1.0;
        int toolbarTranslationZ = 100;
        int viewPagerTranslationZ = 99;
        if (position == 0) {
            if (positionOffset == 0) {
                //  position and positionOffset of 0 means we're on the first fragment, which should
                //  have the viewpager on top of the toolbar, so we set the z-index of the toolbar 
                //  to be slightly less than the view pager.
                toolbarTranslationZ = 99;
                viewPagerTranslationZ = 100;
            }

            opacity = positionOffset;
        }

        //  the first parameter of the Color.Argb method is what sets the opacity level
        toolbar.SetBackgroundColor(Color.Argb((int)(opacity * 255), 200, 84, 59));
        toolbar.SetTitleTextColor(Color.Argb((int)(opacity * 255), 255, 255, 255));

        //  just setting the opacity will hide the toolbar, but we'll also set its
        //  z-index for good measure, and to guarantee it doesn't intercept touch events
        //  while it's invisible.
        ViewCompat.SetTranslationZ(viewPager, viewPagerTranslationZ);
        ViewCompat.SetTranslationZ(toolbar, toolbarTranslationZ);
    }

    public void OnPageScrollStateChanged(int state) {
    }

    public void OnPageSelected(int position) {
    }

    protected override void OnCreate(Bundle savedInstanceState) {
        base.OnCreate(savedInstanceState);

        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.Main);

        var fragments = new List<Fragment> {
            new FragmentOne(),
            new FragmentTwo(),
        };

        toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
        SetActionBar(toolbar);
        ActionBar.Title = "StoriKnow Toolbar";

        viewPager = FindViewById<ViewPager>(Resource.Id.viewPager);
        viewPager.Adapter = new ViewPageFragmentAdapter(FragmentManager, fragments);
        viewPager.AddOnPageChangeListener(this);
    }

    public class ViewPageFragmentAdapter : FragmentPagerAdapter {
        public ViewPageFragmentAdapter(FragmentManager fragmentManager, List<Fragment> fragments) : base(fragmentManager) {
            this.fragments = fragments;
        }

        private readonly List<Fragment> fragments;

        public override int Count => fragments.Count;

        public override Fragment GetItem(int position) {
            if (position < 0) {
                position = 0;
            }

            if (position >= Count) {
                position = Count - 1;
            }

            return fragments[position];
        }
    }
}

Conclusion

There you have it, we’ve successfully utilized a custom toolbar overlay on a view pager and tied its opacity level to the current pages position and offset.

Working Around the Android Device Monitor File Explorer Bug

Working Around the Android Device Monitor File Explorer Bug

Using Xamarin and Visual Studio I created a sample application to test out the new Camera2ApI. The application is straightforward: provide a stream from the camera and persist the image when a button is clicked. After the image is saved it’d be nice to actually see it on the file system; that’s where the Android Device Monitor comes in. The Android Device Monitor is a debugging and analysis tool which provides a file explorer that enables you to navigate your device’s system. Unfortunately, my file explorer always returned an empty result.

After researching a bit I found I wasn’t the only one experiencing this issue. This has even been reported to the Android team and accepted as a known issue. There hasn’t been a bug fix released to date, but there is a workaround which I’ll be covering in this post.

Create a Supported Android Virtual Device

This bug was introduced in the API 24 release and still exists today. Fortunately, we can still make use of the file explorer by creating a new Android Virtual Device that targets API 23 or earlier. These instructions will be geared toward a Visual Studio audience, but should translate well to the other platforms:

Already have a Virtual Device that targets API 23 or less? Fantastic, you can skip these steps entirely and use that virtual device when you need the file explorer.

  1. In Visual Studio, open the Android SDK Manager: Tools -> Android -> Android SDK ManagerOpen Android SDK Manager
  2. Under any API that’s less than 24 (I chose API 21 here), install the SDK Platform and at least one System ImageAndroid SDK Manager Install
  3. Open the Android Emulator Manager (also called the Android Virtual Device Manager): Tools -> Android -> Android Emulator ManagerOpen Android Emulator Manager
  4. On the right-hand side of the Android Emulator Manager, click the Create button to begin creating a new virtual device. You may create an entirely different device than pictured below, and that’s alright! The point here is to create one that targets API 23 or less.Create Android Virtual Device

Now that we have a virtual device created, let’s verify that our solution works.

Verify the Android Device Monitor File Explorer Works

If all goes well we should be able to explore the files on our new virtual device using the Android Device Monitor file explorer.

  1. In the Android Virtual Device Manager (Tools -> Android -> Android Emulator Manager), start your newly created virtual deviceStart a Virtual Device in Android Device Manager
  2. Once the emulator starts, navigate to the Android Device Monitor: Tools -> Android -> Android Device Monitor, select the loaded emulator (emulator-5554 below) and view the File Explorer tab
    Android Device Monitor File Explorer

There we have it, the file explorer is back in action!

Conclusion

The Android Device Monitor is a highly valuable weapon in our arsenal, and will continue to be so. Bugs like this happen and will eventually go away, but until then we have a viable workaround for those of us who are able to target a lower API.