現状のオフライン対応の仕組み

はじめに

以下のプロジェクトを「docker compose up –build」で起動した後、

https://github.com/ki-hi-ro/shopping_list_app

同一IPアドレス:ポート番号でスマホでアクセスできる。

機内モードに変更

機内モードにすると、以下のようになる。

オフラインモード(変更は後で同期)が表示されている。

機内モードを外す

機内モードを外すと、オフラインモードの文字が消える。

現状の問題点

5Gのモバイル通信で、オフライン操作が行えない。

具体的には、商品の追加ができない。

同一IPセグメントにいないためと思われる。

機内モードにしたときに、オフラインモードの表示が行われる理由

それは、frontend/src/components/ShoppingList.vueで以下の定義をしているから。

<template>
  <div>

    <div v-if="offline" style="margin:8px 0; font-size:12px; opacity:.7;">オフラインモード(変更は後で同期)</div>

  </div>
</template>

v-if

v-if=”offline”で、offlineの時に、この要素が表示されるという指定をしている。

    <div v-if="offline" style="margin:8px 0; font-size:12px; opacity:.7;">オフラインモード(変更は後で同期)</div>

リアクティブ変数 : offline

このofflineは以下のように定義されていた。

<script setup>

const offline = ref(!navigator.onLine)

window.addEventListener('online', () => { offline.value = false; flushQueue() })
window.addEventListener('offline', () => { offline.value = true })

</script>

offlineという変数は、TrueかFalseになる。

インターネット接続されている時に、navigator.onLineがTrueになる。

!navigator.onLineは、インターネット接続されていないときにTrueになる。

それをrefで囲むことで、Vueのリアクティブ変数というものになる。

つまり、インターネット接続がされていない時、offlineは、Trueになる。

const offline = ref(!navigator.onLine)

オンラインイベント検知

インターネット接続のイベントを検知すると、offlineがfalseになる。

そして、flushQueue関数というものが発火する。

window.addEventListener('online', () => { offline.value = false; flushQueue() })

flushQueue関数

ShoppingList.vueに定義されている。

<script setup>

// オンライン復帰時:キューを順に実行
async function flushQueue() {
  let q = loadQueue()
  if (!q.length) return
  for (const job of q) {
    try {
      if (job.type === 'createSection') {
        await axios.post('/api/sections/', { list: listId, title: job.payload.title })
      } else if (job.type === 'updateSection') {
        // tmp-id の可能性:最新状態取得後に反映されるのでスキップしてもOK
        const { id, title } = job.payload
        if (!(''+id).startsWith('tmp-sec-')) await axios.patch('/api/sections/'+id+'/', { title })
      } else if (job.type === 'removeSection') {
        const { id } = job.payload
        if (!(''+id).startsWith('tmp-sec-')) await axios.delete('/api/sections/'+id+'/')
      } else if (job.type === 'createItem') {
        const { sectionId, name, qty } = job.payload
        // tmp section の時は最新pull後に作成し直されるので一旦後回しにできる
        if (!(''+sectionId).startsWith('tmp-sec-')) {
          await axios.post('/api/items/', { section: sectionId, name, qty, position: 9999 })
        }
      } else if (job.type === 'toggleItem') {
        const { id } = job.payload
        if (!(''+id).startsWith('tmp-item-')) await axios.post('/api/items/'+id+'/toggle/', { hard_delete: true })
      } else if (job.type === 'reorder') {
        const { sectionId, ids } = job.payload
        if (!(''+sectionId).startsWith('tmp-sec-') && !ids.some(i => (''+i).startsWith('tmp-item-'))) {
          await axios.post(`/api/sections/${sectionId}/reorder/`, { item_ids: ids })
        }
      }
      // 成功したjobは削除
      q = loadQueue().filter(j => j.id !== job.id); setQueue(q)
    } catch (e) {
      // 失敗したら残して終了(次回オンラインで再試行)
      break
    }
  }
  await fetchSectionsOnline()
}

onMounted(async () => {
  try {
    if (!offline.value) {
      await ensureListOnline()
      await fetchSectionsOnline()
      await flushQueue()
    } else {
      loadSectionsOffline()
    }
  } catch (e) {
    // APIに繋がらない場合はオフライン扱いでローカル読込
    offline.value = true
    loadSectionsOffline()
  }
})
</script>

オフライン中に溜め込んだ操作をオンライン復帰後に行っている。

オフラインイベント検知

インターネット未接続のイベントを検知すると、offlineがtrueになる。

window.addEventListener('offline', () => { offline.value = true })

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です