オフラインファーストのモバイルアプリを苦痛なく構築する

Alexander Stigsenは、Realmの共同創設者兼CEOです。

スマートフォンを持っているユーザーは、より良い接続を望んでいるに違いないということは、広く認められている真実です。数十億ドルのインフラストラクチャへの投資と絶え間ない技術革新にもかかわらず、接続された時代の本質的な現実に気付くのに、車ですぐに行くことができます。ネットワーク接続がいつでも利用できるとは限りません。モバイル開発者として、無視するのが便利なのは真実です。

アプリのオフライン状態は処理が混乱する可能性がありますが、問題は基本的で誤った仮定から始まります。つまり、オフラインはデフォルトでエラー状態です。これは、専用のイーサネットアップリンクを備えたデスクトップコンピューター用のアプリを構築したときに意味がありました。エレベーターのドアを閉めるとアプリが完全に役に立たなくなる場合や、信頼性の高いセルラーインフラストラクチャがない場所でアプリケーションが使用されると予想するのが妥当な場合は、意味がありません。

世界をカバーすることはできないので、別の方法を提供する必要があります。オフラインファーストで考える必要があります。オフラインで役立つアプリを設計する必要があります。インターネットが利用可能になったときにそれを最大限に活用するアプリを構築する必要がありますが、インターネットアクセスは常に一時的なものであることを理解しています。オフライン状態を含むスマートな設計上の決定を行い、それらのオフライン状態をユーザーが理解できるようにする必要があります。

オフラインファーストの未来を定義するために、多くの作業が行われています。私が働いている会社であるRealmは、オフラインファーストのモバイルアプリ用のリアルタイムプラットフォームをしばらくの間構築してきました。当社のモバイルデータベースとレルムモバイルプラットフォームにより、ほぼすべてのモバイルデバイスでインテリジェントなオフラインファーストのアプリを簡単に作成できます。 A List Apartの人々は、特にWebアプリについて、オフラインファーストの文献に多大な貢献をしてきました。また、主要なモバイルエコシステムの開発者コミュニティは、独自の印象的なオープンソースソリューションを提供するために何時間も費やしてきました。

以下は、オフラインファーストのモバイルアプリを構築する方法の簡単な紹介です。最小限のオフラインファーストアプリがどのように見えるかを示すために、最後にいくつかの簡単なSwiftサンプルコードを利用しますが、ここで提供される原則と問題は、モバイルアプリ開発で働くすべての人に関連しています。

オフラインファースト向けの設計

常に望んでいたオフラインファーストのアプリを構築する前に、オンラインになる可能性が非常に高いデスクトップに適した設計ソリューションを再検討する必要があります。アプリがオフライン状態とオンライン状態を処理できる場合、アプリで何ができるか、ユーザーに何が可能かをどのように示すかについて質問があります。

オフラインで可能なことを定義する

例としてTwitterを取り上げましょう。オフラインでツイートを投稿する場合、オフラインファーストのTwitterクライアントは2つのパスを取る可能性があります。接続が回復するまでツイートをキューに入れることができます。または、Tweetbotのように、お気に入りなどの他のアクションをキューに入れることができる場合でも、ツイートを拒否する可能性があります。

Tweetbotがオフラインでのツイートを妨げるのはなぜですか?おそらく、オンラインに戻るまでに、ツイートは関連性がなくなっている可能性があります。この問題を解決するには、まだ投稿していないツイートのリストに新しいUIを作成する必要がありますが、オンラインにする前に編集または削除する必要がある場合があります。一方、ツイートを心に留めている場合は、より多くの情報が表示された場合に元に戻すことはほとんどありません。また、投稿のキューに入れられていることを示すだけでも問題は少なくなります。

オフラインアプリにオンラインアプリでできることをすべて実行させることはできませんが、便利にすることはできます。

競合を設計する

変更を調整するためにバックエンドで使用する戦略に関係なく、アプリは2つの競合するデータがあるポイントに直面します。サーバーがクラッシュしたか、あなたと他の人がオフラインで変更を加えて同期したいことが原因である可能性があります。なんでも起こる可能性がある!

したがって、競合を予測し、予測可能な方法でそれらを解決するよう努めます。選択肢を提供します。そして、そもそも衝突を避けるようにしてください。

予測可能であるということは、ユーザーが何が起こり得るかを知っていることを意味します。ユーザーがオフライン時に一度に2つの場所で編集するときに競合が発生する可能性がある場合は、オフライン時にそのことを警告する必要があります。

選択肢を提供するということは、単に最後の書き込みを受け入れたり、変更を連結したり、最も古いコピーを削除したりすることではありません。これは、ユーザーに適切なものを決定させることを意味します。

最後に、最善の解決策は、そもそも競合が発生しないようにすることです。多分それは、多くのソースからの新しくて奇妙なデータが競合を引き起こさず、代わりにあなたが望むように正確に表示されるような方法でアプリを構築することを意味します。これは、オンラインとオフラインの書き込みアプリでは難しいかもしれませんが、共有の描画アプリは、同期されるたびに描画に新しいパスを追加するように設計できます。

明確にする

ユーザーがオフラインでできることを定義することは1つのことです。他のすべての問題は、それらの決定をユーザーが理解できるようにすることです。データと接続の状態、または特定の機能の可用性を正常に伝達できないことは、そもそもオフラインファーストのアプリを構築できなかったことと同じです。

共有のメモ取りアプリが問題を示しています。オフラインになっても、共同編集者が不在のときにアプリで編集を続けることを期待している場合は、ユーザーが満足するまで入力を続けることを許可するだけでは不十分です。彼らが再接続するとき、彼らは発展した対立に驚かれることでしょう。

代わりに、ユーザーが正しい決定を下せるように支援してください。アプリのトップバーの色が変わったためにサーバー接続が切断された場合は、何が起こる可能性があるかがわかります。マージの競合です。ほとんどの場合、これで問題ない可能性があります。アプリのUIは、オンラインに戻ったときに予期しない競合を修正するのに役立ちます。しかし、複数の人がアプリを編集しているときに接続が失われた場合、競合のリスクがはるかに大きいことを知っておくと役に立ちませんか?「あなたは接続を失いましたが、他の人が編集していました。編集を続けると、競合が発生する可能性があります。」ユーザーは続行できますが、リスクはわかっています。

設計の問題と解決策について際限なく書くのは簡単ですが、使用する必要のあるツールから離れる前に、オフラインファーストのモバイルアプリを構築するのがどのようなものかを確認すると役立つ場合があります。

Realmを使用してオフラインファーストのアプリを構築する

基本的なオフラインファーストアプリのアーキテクチャは派手ではありません。アプリ内のデータを永続化する方法(デバイス上のデータベースを使用)、サーバーと通信するためのプロトコル(必要に応じてシリアル化および逆シリアル化コードを含む)、および同期されたデータが存在するサーバーが必要です。許可を得た人に配布されます。

最初に、iOSアプリ内でレルムモバイルデータベースの使用を開始する方法について説明します(ただし、Androidアプリではコードの見た目はそれほど変わりません)。次に、サーバーから取得してローカルレルムデータベースに格納するコードをシリアル化および逆シリアル化するための戦略を示します。最後に、リアルタイムで同期する共同ToDoリストアプリですべてを連携させる方法を紹介します。

レルムモバイルデータベース

Realmを使い始めるのは簡単です。Realm Mobile Databaseをインストールしてから、クラスを作成してスキーマを定義します。Realmはオブジェクトデータベースであるため、クラスを作成し、いくつかのオブジェクトをインスタンス化し、それらのオブジェクトをwriteブロックに渡してディスクに永続化するのと同じくらい簡単です。シリアル化やORMは必要ありません。さらに、AppleのCoreDataよりも高速です。

これが私たちのモデルのコアであり、最も基本的なTo Doリストアプリです(新しいタスクを作成するたびに再コンパイルする必要があります)。

import RealmSwift

class Task: Object {

   dynamic var name

}

class TaskList: Object {

   let tasks = List()

}

let myTask = Task()

myTask.task

let myTaskList = TaskList()

myTaskList.tasks.append(myTask)

let realm = Realm()

try! realm.write{

            realm.add([myTask, myTaskList])

}

そこから、より完全に機能するアプリを構築するのにそれほど時間はかかりませんTableViewController

import UIKit

import RealmSwift

class TaskListTableViewController: UITableViewController {

   var realm = try! Realm()

   var taskList = TaskList()

   override func viewDidLoad() {

       super.viewDidLoad()

       print(Realm.Configuration.defaultConfiguration.fileURL!)

       // Here, you could replace self.taskList with a previously saved TaskList object

       try! realm.write {

           realm.add(self.taskList)

       }

       // add navbar +

       navigationItem.setRightBarButton(UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.add, target: self, action: #selector(displayTaskAlert)), animated: false)

   }

   func displayTaskAlert() {

       // make and display an alert that’ll take a name and make a task.

       let alert = UIAlertController(title: “Make a task”, message: “What do you want to call it?”, preferredStyle: UIAlertControllerStyle.alert)

       alert.addTextField(configurationHandler: nil)

       alert.addAction(UIAlertAction(title: “Cancel”, style: UIAlertActionStyle.cancel, handler: nil))

       alert.addAction(UIAlertAction(title: “Create Task”, style: UIAlertActionStyle.default, handler: { (action) in

           let task = Task()

           task.name = (alert.textFields?[0].text)!

           try! self.realm.write {

               self.realm.add(task)

               self.taskList.tasks.append(task)

           }

           self.tableView.reloadData()

       }))

       self.present(alert, animated: true, completion: nil)

   }

   override func didReceiveMemoryWarning() {

       super.didReceiveMemoryWarning()

   }

   override func numberOfSections(in tableView: UITableView) -> Int {

       return 1

   }

   override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

       return self.taskList.tasks.count

   }

   override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

       let cell = tableView.dequeueReusableCell(withIdentifier: “reuseIdentifier”, for: indexPath)

       cell.textLabel?.text = self.taskList.tasks[indexPath.row].name

       return cell

   }

}

始めるのに必要なのはそれだけです!Realmのコレクションとオブジェクト通知を使用すると、はるかに賢くtableViewなり、オブジェクトが追加または削除されたときにインテリジェントにリロードできますが、現時点では、オフラインファーストアプリの基盤である永続性があります。

シリアル化と逆シリアル化

オフラインファーストのアプリは、オンラインに接続できない限り、オフラインファーストのアプリではありません。レルムとの間でデータを取得するのは少し難しい場合があります。

まず、クライアントスキーマをサーバーのスキーマにできるだけ一致させることが重要です。ほとんどのバックエンドデータベースがどのように機能するかを考えると、Realmオブジェクトにはデフォルトで主キーがないため、Realmクラスに主キーフィールドを追加する必要があります。

スキーマを適切に一致させたら、サーバーからのデータをレルムに逆シリアル化し、データをJSONにシリアル化してサーバーに送り返す方法が必要です。それを行う最も簡単な方法は、お気に入りのモデルマッピングライブラリを選択して、手間のかかる作業を行わせることです。Swiftには、Argo、Decodable、ObjectMapper、およびMapperがあります。これで、サーバーから応答を受け取ったら、モデルマッパーにそれをネイティブRealmObjectにデコードさせるだけです。

それでも、それはそれほど素晴らしい解決策ではありません。そもそもサーバーとの間でJSONを安全にやり取りするには、大量のネットワークコードを作成する必要があります。また、モデルマッパーコードは、スキーマが変更されるたびに書き直してデバッグする必要があります。より良い方法があるはずです、そして私たちはレルムモバイルプラットフォームがまさにそれであると思います。

レルムモバイルプラットフォームの操作

Realm Mobile Platform(RMP)を使用すると、リアルタイムの同期が可能になるため、サーバーとアプリを対話させるために戦うのではなく、モバイルアプリの構築に集中できます。上記のレルムモデルを使用して、RMPのユーザー認証を追加し、サーバーとアプリのレルム間のデータの同期をRMPに任せるだけです。次に、ネイティブのSwiftオブジェクトを引き続き使用します。

開始するには、Realm Mobile Platform MacOSバンドルをダウンロードしてインストールします。これにより、MacでRealm ObjectServerインスタンスをすばやく実行できます。次に、やることリストアプリにいくつかの項目を追加して、レルムオブジェクトサーバーに接続します。

上記のインストール手順を完了すると、サーバーが実行され、管理者ユーザーが//127.0.0.1:9080になります。これらの資格情報を覚えておいてください。Swiftコードに戻ります。

これ以上コードを書く前に、プロジェクトに2つの小さな変更を加える必要があります。まず、Xcodeでアプリのターゲットエディターに移動し、[機能]タブで[キーチェーン共有]スイッチを有効にする必要があります。

次に、非TLSネットワークリクエストを許可する必要があります。プロジェクトのInfo.plistファイルに移動し、タグ内に以下を追加します。

NSAppTransportSecurity

   NSAllowsArbitraryLoads