Building an offline-friendly picture upload-system-smashing magazine

Building an offline-friendly picture upload-system-smashing magazine

So you fill out an online form and it asks you to upload a file. You click Input, select a file from your desktop and are good to go. But something happens. The network drops, the file disappears and you are stuck by having to upload the file again. Poor network connection Can lead to you spending an unreasonable amount of time trying to upload files successfully.

What destroys the user experience stems from constantly having to control network stability and try again uploading several times. Although we may not be able to do much about networking, as developers, we can always do something to ease the pain that comes with this problem.

One of the ways we can solve this problem is by tuning image upload systems in a way that allows users to upload images offline – Removing the need for a reliable network connectionAnd then have the system again the upload process again when the network becomes stable without the user intervening.

This article will focus on explaining how to build An offline-friendly image upload system Using PWA (progressive web application) technologies such as IndexedDBService workers and background sync API. We will also briefly cover tips for improving the user experience to this system.

Planning of offline image upload system

Here is a flow chart for an offline-friendly image upload system.

Flow-Diagram over an offline-friendly picture upload system (large preview)

As shown in the flow chart, the process takes place as follows:

  1. The user selects an image.
    The process begins by letting the user choose their image.
  2. The picture is stored locally in IndexedDB.
    Next, checks the system for network connection. If network connection is available, the system uploads the image directly and avoids unnecessary local storage use. But if the network is not available, the image is stored in IndexedDB.
  3. The service worker detects when the network is restored.
    With the picture tucked in IndexedDBThe system is waiting to detect when the network connection is restored to continue with the next step.
  4. Background synchronization processes pending uploads.
    The moment the connection is restored, the system will try to upload the image again.
  5. The file is successfully uploaded.
    In the moment the image is uploaded, the system removes the local copy that is stored in IndexedDB.

Implementation of the system

The first step of the system implementation is to allow the user to choose their images. There are different ways you can achieve this:

I would recommend that you use both. Some users prefer to use the drag-and-slip interface while others think the only way to upload images is through element. Having both options will help improve the user experience. You may also consider allowing users to insert images directly into the browser using the clipboard API.

Registration of the service worker

In the heart of this solution is the service worker. Our service worker will be responsible for retrieving the picture from IndexedDB Store, upload it when the Internet connection is restored, and clears IndexedDB Store when the image is uploaded.

To use a service worker you must first register one:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(reg => console.log('Service Worker registered', reg))
    .catch(err => console.error('Service Worker registration failed', err));
}

Control of network connection

Remember that the problem we are trying to solve unreliable network connection. If this problem does not exist, there is no sense in trying to solve something. Once the image is selected, we need to check if the user has a reliable internet connection before we detect a synchronization event and save the image in IndexedDB.

function uploadImage() {
  if (navigator.onLine) {
    // Upload Image
  } else {
    // register Sync Event
    // Store Images in IndexedDB
  }
}

Note: I only use navigator.onLine Property here to demonstrate how the system would work. The navigator.onLine property is unreliableAnd I would suggest that you come up with a custom solution to check if the user is connected to the Internet or not. One way you can do this is by sending a ping request to a server point you have created.

Registration of the synchronization event

When the network test fails, the next step is to detect a synchronization event. The synchronization event must be registered at the point where the system does not upload the image due to a poor Internet connection.

async function registerSyncEvent() {
  if ('SyncManager' in window) {
    const registration = await navigator.serviceWorker.ready;
    await registration.sync.register('uploadImages');
    console.log('Background Sync registered');
  }
}

After registering the SYNC event, listen to it in the service worker.

self.addEventListener('sync', (event) => {
  if (event.tag === 'uploadImages') {
    event.waitUntil(sendImages());
  }
});

The sendImages The feature becomes an asynchronous process that will retrieve the image from IndexedDB and upload it to the server. This is what it looks like:

async function sendImages() {
  try {
    // await image retrieval and upload
  } catch (error) {
    // throw error
  }
}

Opening the database

The first thing we need to do to save our image locally is to open one IndexedDB Store. As you can see from the below code we create A global variable to save the database body. The reason for doing this is that when we want to pick up our image from IndexedDBWe don’t have to write the code to open the database again.

let database; // Global variable to store the database instance

function openDatabase() {
  return new Promise((resolve, reject) => {
    if (database) return resolve(database); // Return existing database instance 

    const request = indexedDB.open("myDatabase", 1);

    request.onerror = (event) => {
      console.error("Database error:", event.target.error);
      reject(event.target.error); // Reject the promise on error
    };

    request.onupgradeneeded = (event) => {
        const db = event.target.result;
        // Create the "images" object store if it doesn't exist.
        if (!db.objectStoreNames.contains("images")) {
          db.createObjectStore("images", { keyPath: "id" });
        }
        console.log("Database setup complete.");
    };

    request.onsuccess = (event) => {
      database = event.target.result; // Store the database instance globally
      resolve(database); // Resolve the promise with the database instance
    };
  });
}

Storing the image in indexeddb

With IndexedDB Keep open, we can now save our pictures.

Now you may wonder why a lighter solution like localStorage were not used for this purpose.

The reason for it is IndexedDB Works asynchronously and does not block the most important JavaScript wire, whereas there is localStorage Runs synchronously and can block JavaScript head thread if used.

How to save the picture in IndexedDB:

async function storeImages(file) {
  // Open the IndexedDB database.
  const db = await openDatabase();
  // Create a transaction with read and write access.
  const transaction = db.transaction("images", "readwrite");
  // Access the "images" object store.
  const store = transaction.objectStore("images");
  // Define the image record to be stored.
  const imageRecord = {
    id: IMAGE_ID,   // a unique ID
    image: file     // Store the image file (Blob)
  };
  // Add the image record to the store.
  const addRequest = store.add(imageRecord);
  // Handle successful addition.
  addRequest.onsuccess = () => console.log("Image added successfully!");
  // Handle errors during insertion.
  addRequest.onerror = (e) => console.error("Error storing image:", e.target.error);
}

With the images saved and background synchronization set, the system is ready to upload the image when the network connection is restored.

Retrieval and upload of the images

Once the network connection is restored, the synchronization event is shooting and the service worker retrieves the picture from IndexedDB and upload it.

async function retrieveAndUploadImage(IMAGE_ID) {
  try {
    const db = await openDatabase(); // Ensure the database is open
    const transaction = db.transaction("images", "readonly");
    const store = transaction.objectStore("images");
    const request = store.get(IMAGE_ID);
    request.onsuccess = function (event) {
      const image = event.target.result;
      if (image) {
        // upload Image to server here
      } else {
        console.log("No image found with ID:", IMAGE_ID);
      }
    };
    request.onerror = () => {
        console.error("Error retrieving image.");
    };
  } catch (error) {
    console.error("Failed to open database:", error);
  }
}

Deleting the indexeddb -database

Once the image has been uploaded, IndexedDB Shop is no longer necessary. Therefore, it must be deleted with its contents to release storage.

function deleteDatabase() {
  // Check if there's an open connection to the database.
  if (database) {
    database.close(); // Close the database connection
    console.log("Database connection closed.");
  }

  // Request to delete the database named "myDatabase".
  const deleteRequest = indexedDB.deleteDatabase("myDatabase");

  // Handle successful deletion of the database.
  deleteRequest.onsuccess = function () {
    console.log("Database deleted successfully!");
  };

  // Handle errors that occur during the deletion process.
  deleteRequest.onerror = function (event) {
    console.error("Error deleting database:", event.target.error);
  };

  // Handle cases where the deletion is blocked (e.g., if there are still open connections).
  deleteRequest.onblocked = function () {
    console.warn("Database deletion blocked. Close open connections and try again.");
  };
}

With that, the whole process is complete!

Considerations and limitations

While we have done a lot to help improve the experience by supporting offline uploads, the system is not without its limitations. I figured I would specifically call them out because it is worth knowing where this solution may be under your needs.

  • No reliable Internet connection detection
    JavaScript does not provide a foolproof way of detecting online status. For this reason, come up with a custom solution to detect online status.
  • Only chrome solution
    Background synchronization API is currently limited to chrome -based browsers. As such, this solution is supported only by chrome browsers. This means you need a more robust solution if you have most of your users on non-chrome browsers.
  • IndexedDB Storage policies
    Browsers impose storage restrictions and postponement policies for IndexedDB. For example, in Safari that is stored data in IndexedDB Have a lifetime of seven days if the user does not interact with the site. This is something you need to keep in mind if you come up with an alternative to the Background Sync API that supports Safari.

Improving the user experience

As the whole process happens in the background, we need a way to inform users when images are stored, waiting to be uploaded or have been successfully uploaded. Implementation of certain UI elements In fact, for this purpose will improve the experience of users. These UI elements may include toast notifications, upload status indicators such as spinners (to display active processes), status beams (to show state progress), network status indicators, or buttons to give tests and cancel options.

Wrapping

Poor Internet connection can interfere with the user experience of a web application. By utilizing PWA technologies such as IndexedDBService workers and background sync API can develop developers to improve the reliability of web applications to their users, especially those in areas of unreliable Internet connection.

Smashing editorial
(GG, YK)

Leave a Reply

Your email address will not be published. Required fields are marked *