FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

mBaaSを使ったPWAを作る(その4 通信回数を減らす)

f:id:mbaasdevrel:20180625094227g:plain

最近にわかに盛り上がっているのがPWA(Progressive Web App)です。PWAは固有の技術を指すキーワードではなく、モバイルWebアプリを作るためのベストプラクティスと言えます。

今回から何回かに分けて、mBaaSを使ったPWAを作っていきます。今回は前回まで作ったTodo管理アプリの通信回数を減らしていきます。

前回まで

前回まではタスク追加、更新、削除を行った際に一覧を取得し直していました。また、タスクのステータスごとにデータを取得していました。これだと通信回数が増える上、オンラインとオフライン時の処理分けが増えてしまいます。

そこで、タスクの取得を一回にし、プログラム側でフィルタリングして表示するようにします。また、データの追加/更新/削除処理についてもオンラインとオフライン時を想定してキューに入れる処理を追加します。

キューを追加する処理を追加

まずデータを追加します。

data() {
  return {
    allTasks: [], // すべてのタスクを入れておく変数
    tasks: [],    // 表示するタスクを入れておく変数
    task: {
      text: null
    },
    status: 'active',
    queues: {  // 追加/更新/削除のキュー
      add: [],
      done: [],
      delete: []
    }
  }
},

データ取得処理の分割

元々のデータ取得処理は取得以外にもlocalStorageへの保存や Vue のデータ更新処理を行っていましたので、これを分割します。

async fetch() {
  const tasks = await (navigator.onLine ? this.onlineFetch() : this.offlineFetch());
  const cache = this.getCache();
  cache[this.status] = tasks;
  localStorage.setItem('tasks', JSON.stringify(cache));
  Vue.set(this, 'tasks', tasks);
},

これを以下のようにします。すべてのタスクを取得し、取得後にフィルタリングします。

async fetch() {
  this.allTasks = await (navigator.onLine ? this.onlineFetch() : this.offlineFetch());
  this.filter();
},
save() {
  localStorage.setItem('tasks', JSON.stringify(this.allTasks));
},
filter() {
  const status = this.status;
  Vue.set(this, 'tasks', this.allTasks.filter(task => task.status == status));
  this.save();
},

これに伴ってオンラインデータを取得する処理は検索条件をなくします。

async onlineFetch() {
  return await Task.fetchAll();
},

同様にオフラインキャッシュの処理も簡単になります。

getCache() {
  const str = localStorage.getItem('tasks');
  let tasks = str ? JSON.parse(str) : {};
  tasks = this.jsonToClass(tasks || []);
  return tasks;
},

画面を構築した時に実行される created は、localStorage への保存処理が追加されます。

async created() {
  if (!ncmb.User.getCurrentUser()) {
    await ncmb.User.loginAsAnonymous();
  }
  await this.fetch();
  this.save(); // 追加
},

フィルタ処理の追加

タスク一覧のステータスが変わった時の changeStatus はデータを取得し直しはせず、フィルタリングだけになります。これにより、オンライン通信が減らせます。

async changeStatus(status) {
  this.status = status;
  this.filter();
},

タスク処理のオフライン対応

続いてタスクの追加、更新、削除を行った際のオフライン対応を進めます。

追加処理

追加処理は this.queues.add に処理対象のタスクを追加します。同時に this.allTasks に追加することで、オフライン状態の時でもタスクの追加が確認できます。

ポイントとして、オフライン時には local_ ではじまるobjectIdを付与しています。これはオンラインデータではないというフラグになります。後述する更新処理、削除処理ではobjectIdを対象にデータを探しますので、ユニークになるIDを仮に付与しておきます。

async add() {
  const text = this.task.text;
  let task = new Task;
  task
    .set('text', text)
    .set('status', 'active')
    .set('acl', this.getAcl());
  if (navigator.onLine) {
    task = await task.save();
  } else {
    task.set('objectId', `local_${Math.random().toString(36).slice(-8)}`);
    this.addQueue('add', task);
  }
  this.allTasks.push(task);
  this.task.text = '';
  this.filter();
},

更新処理

更新処理の場合、注意するのは処理対象になるデータを探す処理になるでしょう。処理対象のタスクを allTasks の中から探し、ステータスを更新したデータを適用します。オフラインの場合には this.queues.done の中に追加します。

async done(task, event) {
  const checked = event.target.checked;
  task.set('status', checked ? 'done' : 'active');
  const objectId = task.objectId;
  const index = this.allTasks.findIndex(task => task.objectId == objectId);
  this.allTasks[index] = task;
  if (navigator.onLine) {
    await task.update();
  } else {
    this.addQueue('done', task);
  }
  this.filter();
},

削除処理

削除処理は更新処理とあまり変わりません。ただし配列から対象データを探して削除します。

async destroy(task) {
  const objectId = task.objectId;
  const index = this.allTasks.findIndex(task => task.objectId == objectId);
  this.allTasks.splice(index, 1);
  if (navigator.onLine) {
    await task.delete();
  } else {
    this.addQueue('delete', task);
  }
  this.filter();
}

キューは localStorage に残す

キューは変数に残したままだと再読込した時に消えてしまいます。そうならないために、localStorage に保存しておきます。

addQueue(action, task) {
  this.queues[action].push(task);
  localStorage.setItem('queues', JSON.stringify(this.queues));
},

まとめ

ここまでの処理で、オフラインでもTodoを追加したり、更新、削除できるようになります。

f:id:mbaasdevrel:20180625094227g:plain

もちろん現在はオンラインデータは更新していませんので、オンラインに戻って再読込するとデータが復元してしまいます。

次回はオンライン/オフラインの通知イベントを使って、キューにあるデータを処理します。

ここまでのコードはNCMBMania/PWA-NCMB at v4にアップロードしてあります。実装時の参考にしてください。

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。