March 23rd, 2023

Sync to OneNote in Android app using MS Graph

Hello Android developers,

Today we’ll be finishing up our blog series on using the Microsoft Authentication Library (MSAL) and the Microsoft Graph. Using these tools, your own Android apps will be able to connect to M365 services.

Previously, we covered the basics of MSAL, an introduction to MS Graph for Android, and how to call the MS Graph API in an Android app, so be sure to check out those posts first!

This week, we’d like to share a new open-source sample that shows how you can use MSAL and the MS Graph API in more complex scenarios within an Android app. In fact, this new sample is a revamped version of our old intern project, TwoNote! The updated version of TwoNote supports a one-way sync with OneNote, and in this post we’ll cover:

  • How to run the sample
  • App features

    • Sign in with MSAL
    • Create note
    • Update note
    • Delete note
  • MSAL and MS Graph helper classes

Getting started

As we covered in previous blog posts in the series, to call the MS Graph API using MSAL tokens, there are some setup steps with app registrations and project config files.

If you want to be able to run the sample, you’ll need to follow these steps:

  1. Clone or fork the android-samples Github repo
  2. Follow the instructions to create an app registration in Azure Portal
  3. Open the TwoNote project in Android Studio
  4. Follow the instructions to fill in the configuration files in Android Studio
  5. Build and run the app

If you don’t want to create an app registration for this sample, then you can just take a look at the sample code and documentation to see examples of MS Graph API calls and copy over any necessary code to your own registered projects!

App features

Once you’re able to get the sample up and running, you can check out its main features:

  • Sign in/out with MSAL
  • Create new note
  • Update existing note
  • Delete note

In this sample, we chose to use the MS Graph Java SDK to build requests. Unlike the simple code snippets from last week’s blog post, in this sample we chose to store the user’s MSAL authentication token upon sign in. Since we don’t have to acquire a new token for every request, we don’t have to pass in callback objects to handle the request results.

Sign in

To start, there’s a new icon in the top app bar that lets you sign in and out of a Microsoft account. Depending on whether you’re logged in or not, the icon will show the action you can perform.

Figure 1. Depending on your login state, you’ll either see a sign in or sign out icon in the TwoNote top app bar.

If you are trying to sign in, you’ll be prompted by MSAL to enter your username/email. If there’s already cached authentication information, then you’ll be able to sign in immediately. If not, you’ll be prompted for your password and two-factor authentication. Once you’re signed in, note changes will be synced to OneNote.

Keep in mind that for this sample, any offline changes made in the app will not be synced to OneNote after the user signs in.

To sign out, all you need to do is click the sign out icon. Once you do this, any note changes will no longer be synced to OneNote and TwoNote will operate as a standalone note taking app like previous versions.

In the MSAL blog post, we already covered the implementation details for prompting a user to sign in. The code for signing out is even simpler – all you have to do is call signOut with your PublicClientApplication instance. Like with sign in, you need to pass in a callback to handle the success and error cases:

  val signOutCallback = object : ISingleAccountPublicClientApplication.SignOutCallback {
  override fun onSignOut() {
  	// TODO: on successful sign out
  }
  override fun onError(exception: MsalException) {
  	// TODO: on error
  }
  // mSingleAccountApp is our PublicClientApplication instance
  mSingleAccountApp?.signOut(signOutCallback)

Create note

Once a user is logged in, note changes will start being synced to OneNote. To accomplish this, we added a new onenotePageId field to the INode model in the app, which defaults to a blank string. If a note has never been synced to OneNote before, meaning the onenotePageId is blank, then TwoNote will make a create page request to create a new page in the first section of the user’s default notebook. This request is triggered when the user presses the back button to save the note.

[crop output image]

Figure 2. When you create and save a new note in TwoNote, note contents will automatically be synced to OneNote. Upon completion, a snackbar notification will show up with a link to your new OneNote page!

In terms of implementation, the create page request requires more complex setup than the display name request from the previous blog post. Since it’s a POST request, our first task was to figure out how to format the request body. According to the documentation, the request body “can contain HTML placed directly in the request body, or it can contain a multipart message format as shown in the example. If you’re sending binary data, then you must send a multipart request.”

TwoNote currently supports the syncing of note title, text, and up to one image. Since we decided to support image data, we had to do some investigation into how to build a multipart request. We ended up following instructions from the documentation to add an image using binary data, and you can see the resulting implementation in our createPage method:

  private fun createPage(pageMultipart: Multipart, sectionId: String): CompletableFuture<OnenotePage> {
          val body = pageMultipart.content()
              ?: throw IllegalArgumentException("Error: null multipart body, cannot make request to create page")
          return graphClient!!.me().onenote().sections(sectionId).pages()
              .buildRequest(
                  HeaderOption(
                      "Content-Type",
                      "${ContentType.MULTIPART_FORM_DATA.mimeType}; boundary=${pageMultipart.boundary}"
                  )
              )
              .postAsync(body)
  }

As you can see in the code snippet, once the multipart request body has been successfully built, all that’s left is to add the multipart request header details and make the request.

Update note

If the onenotePageId field that we mentioned earlier is not blank for a note, then instead of creating a new page, TwoNote will make an update page request. Like with creating pages, this request is triggered when the user presses the back button to save the note. Similarly, TwoNote specifically only supports syncing the note title, text, and up to one image.

[crop output image]

Figure 3. If a note is already linked to an existing OneNote page, any saved changes will automatically be synced to OneNote.

Unlike the simple HTML request body used for creating notes, each MS Graph update request is an array of JSON change objects, each of which contains an id of the OneNote element to change and the HTML content for that update.

Here is how a simple JSON request body for changing the title would look:

  [
      {
          "target":"title",
          "action":"replace",
          "content":"New title"
      }
  ]

For more complex scenarios, including retrieving object identifiers for updates and updating images with multipart requests, visit the Update OneNote page content documentation.

Constructing the JSON body for an update request is relatively straightforward when sending MS Graph requests via Volley. The MS Graph Java SDK however, currently has a known issue that results in failed requests. For more information, you can check out Github issue. In order to work around this issue, we added the file ModifiedOnenotePageOnenotePatchContentRequest.kt as a stopgap solution until a fix is made in the library.

Delete note

Finally, when the onenotePageId field contains a valid id, and a user deletes the note in app, then TwoNote will make a delete page request to ensure the contents are also deleted from OneNote.

[crop output image]

Figure 4. When deleting notes in TwoNote, any linked pages in OneNote will also automatically be deleted.

Compared to the previous features, setting up this request was much simpler! All we had to do was retrieve the onenotePageId and pass it into our request, as shown in the deletePage method:

graphClient!!.me().onenote().pages(onenotePageId).buildRequest().deleteAsync()

Helper classes

Even though this open-source sample mostly focuses on making OneNote-related requests, we wanted to make sure it would still be a useful reference for any Android devs looking to incorporate MSAL and MS Graph into their own apps.

That’s why we decided to create several helper classes that you can copy into your own projects and modify based on your needs!

If you’re looking for code snippets to reference, the majority of the MSAL and MS Graph code will be located in these three files:

  • BaseGraphHelper.kt – Initializes MSAL config and contains instance of GraphClient, also provides implementation for basic actions like signing in/out, requesting tokens, etc.
  • NotesGraphHelper.kt – Implements BaseGraphHelper and provides implementation for notes-specific requests, like creating a page, updating a page, deleting a page, etc.
  • GraphViewModel – Contains instance of NotesGraphHelper and connects UI/app logic to request helper methods

And with that, we conclude this blog series 🥳 We hope these tutorials and resources have been helpful to you, and we can’t wait to see how you use MSAL and MS Graph in your own Android apps!

Resources and feedback

To learn more about using MSAL and MS Graph in Android apps, check out:

If you have any questions, or would like to tell us about your apps, use the feedback forum or message us on Twitter @surfaceduodev.

Finally, livestreaming is back! Join us at 11am Pacific time Friday 24th March at twitch.tv/SurfaceDuoDev, and out the archives on YouTube.

Author

Parker Schroeder
SW/FW Engineer

Works in the Surface Duo Developer Experience team to help with all aspects of dual-screen SDK development and customer engagement.

Kristen Halper
SW/FW Engineer

Works in the Surface Duo Developer Experience team to help with all aspects of dual-screen SDK development and customer engagement.

0 comments

Discussion are closed.