『記憶を日常に。』を実現するための大切な仕組み。Monoxerのオフライン学習とは

モノグサエンジニアの井岡です。 モノグサでも技術的な情報を技術ブログとして発信していきます。プロダクトのMonoxerに関連することだけでなく、モノグサ社員が趣味で行っている内容等についても紹介していく予定です。

記念すべき第1回は、Monoxerの特徴的な機能であるオフライン学習についてご紹介します。

Monoxerとは

Monoxerは解いて憶える記憶アプリです。憶えたい内容を登録すると、記憶している状況(=記憶度)に応じてアプリが問題を生成します。まったく憶えていないときは写経形式(薄く答えが出ている状態で回答)で、少し憶えているときは択一形式で、すでに記憶していそうであれば自由入力形式で、といったように記憶度に応じて出題形式が変化します。そのため無理なく自然に憶えることができます。

出題形式をコントロールするための重要な機能として記憶の一元管理*1があります。簡単に説明すると、問題を要素に分解し、それぞれの記憶度を管理するための機能です。例えば、数式の「2+3」であれば整数「2」、「3」と、「足し算」の要素に分解できます。これらを記憶している状況で、「3+4」を出題する際には、整数「3」と「足し算」は記憶しているとみなして、出題形式を決定しています。

管理機能も充実しており、塾や学校の先生が生徒に課題(=タスク)を配信できます。配信したタスクを生徒が学習すると、その学習結果や記憶度を逐次サーバーに送信していきます。そのため、生徒ごとの進捗や記憶度をリアルタイムに確認することができます。

f:id:corp_monoxer:20210819163503p:plain

オフライン学習の仕組み

Monoxerを用いた記憶は日常的に行われるものであり、飛行機や地下鉄の中、山でのキャンプ中等のように通信が不安定な環境下でも学習できる必要があります。もちろん、タスク配信時にはサーバーからデータを取得する必要がありますが、学習については可能な限りオフラインでも実行できるような仕組みにしています。

Monoxerでの学習は以下のような流れで行います。

  1. サーバーからタスクをダウンロード (初回のみ)
  2. タスクを学習
  3. 学習結果・記憶度をローカルに保存
  4. 学習結果・記憶度をサーバーに送信 f:id:corp_monoxer:20210819163527p:plain

サーバーに確実にデータが保存された状態にするために、データがサーバーに保存されたかどうかをdirtyフラグを用いて管理しています。ローカルに保存するタイミングではdirtyフラグをtrueにし、サーバーへの保存が成功した場合にfalseに変更します。オフライン時には上記4.のサーバーへの送信が失敗するため、dirtyフラグがtrueのままローカルに保存された状態になります。 f:id:corp_monoxer:20210819163530p:plain

その後、端末がオンラインに変わったタイミング*2で、ローカルに保存されている学習結果や記憶度のうちdirtyフラグがtrueのものをすべて取得し、あらためてサーバーに送信します。

f:id:corp_monoxer:20210819163523p:plain

また、記憶度は最新のものがサーバーに保存された状態になっています。Monoxerでは複数端末での学習も可能なため、保存されているものと受け取ったもののうち新しいものが保存されるようにタイムスタンプのチェックも行っています。

他にもサーバーでの保存は成功しているが、レスポンスを受け取れないということも起こり得ます。この場合、ローカルに保存されている学習結果のdirtyフラグはtrueのままであるため再度サーバーにデータが送信されますが、サーバーではすでに同じデータが保存されているため、重複して保存されないようにする必要があります。そのため、サーバーで管理しているIDとは別に、ローカルでユニークIDを生成し、これを比較して保存済みかを判断しています。

まとめ

Monoxerでは日常的に記憶するために様々な機能を提供しています。今回はその中のオフライン学習についてご紹介しました。様々な状況での学習に対応するために、整合性を取れるようにデータを管理しています。 他の機能については、別の機会にご紹介できればと思います。

*1:記憶の一元管理の詳細はCTOのインタビュー記事をご確認ください。

*2:iOSではReachabilityのwhenReachableおよびwhenUnreachableを用いて通信可否の変更を検知しています。Androidではandroid.net.ConnectivityManagerのNetworkCallbackを用いて通信可否の変更を検知しています。