DEV Community

Takuya Matsuyama
Takuya Matsuyama

Posted on

Created PouchDB@7 for React Native

About 2 years ago, I hacked PouchDB@6 to make it able to run on React Native with attachments support. But the current latest version is 7 now.
Unfortunately, the PouchDB community is not interested in supporting React Native.
So, we have to hack it again for getting v7 to work on RN.

This is the work for building my app called Inkdrop - a cross-platform Markdown note-taking app that uses PouchDB for accomplishing its data sync feature.

How to use

Check out the fork repository:

GitHub logo craftzdog / pouchdb-react-native

🐨 - PouchDB is a pocket-sized database, with some patches for running on React Native

PouchDB for React Native

Build Status Coverage Status Greenkeeper badge npm version jsDelivr Hits

A PouchDB fork for React Native with binary attachments support.

Using PouchDB

Check out a small example.

Install

  1. Install dev package:

    yarn add -D babel-plugin-module-resolver
    
  2. Install polyfill packages:

    yarn add events process base-64 react-native-get-random-values react-native-quick-md5
    Enter fullscreen mode Exit fullscreen mode
  3. Install pouchdb packages:

    yarn add @craftzdog
    /pouchdb-core-react-native @craftzdog
    /pouchdb-binary-utils-react-native pouchdb-adapter-http pouchdb-mapreduce pouchdb-replication react-native-pouchdb-md5
    Enter fullscreen mode Exit fullscreen mode

    Note: @craftzdog/pouchdb-replication-react-native is no longer needed.

  4. Install storage adapter packages:

    yarn add pouchdb-adapter-react-native-sqlite react-native-sqlite-2
    Enter fullscreen mode Exit fullscreen mode
  5. Install CocoaPods:

    cd ios && pod install
    Enter fullscreen mode Exit fullscreen mode

Configure

  1. Make a shim.js:

    import "react-native-get-random-values";
    import { decode, encode } from "base-64";
    if (typeof process === "undefined") {
      global.process = require("process");
    } else {
      const bProcess = require("process");
      for (var p in bProcess) {
        if (!(p in process)) {
          process[p] = bProcess[p];
        }
      }
    }
    if (
    Enter fullscreen mode Exit fullscreen mode

Also check out a small example.

Install

Install dev package:

yarn add -D babel-plugin-module-resolver
Enter fullscreen mode Exit fullscreen mode

Install polyfill packages:

yarn add events process base-64 react-native-get-random-values react-native-quick-md5
Enter fullscreen mode Exit fullscreen mode

Install pouchdb packages:

yarn add @craftzdog/pouchdb-core-react-native @craftzdog/pouchdb-binary-utils-react-native pouchdb-adapter-http pouchdb-mapreduce pouchdb-replication react-native-pouchdb-md5
Enter fullscreen mode Exit fullscreen mode

Note: @craftzdog/pouchdb-replication-react-native is no longer needed.

Install storage adapter packages:

yarn add pouchdb-adapter-react-native-sqlite react-native-sqlite-2
Enter fullscreen mode Exit fullscreen mode

Install CocoaPods:

cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

Configure

Make a shim.js:

import "react-native-get-random-values";
import { decode, encode } from "base-64";

if (typeof process === "undefined") {
 global.process = require("process");
} else {
 const bProcess = require("process");
 for (var p in bProcess) {
   if (!(p in process)) {
     process[p] = bProcess[p];
   }
 }
}

if (!global.btoa) {
 global.btoa = encode;
}

if (!global.atob) {
 global.atob = decode;
}

process.browser = true;
Enter fullscreen mode Exit fullscreen mode

then, require it at the beginning of your index.js.

Edit your babel.config.js like so:

module.exports = {
 presets: ['module:metro-react-native-babel-preset'],
 plugins: [
   [
     'module-resolver',
     {
       alias: {
         'pouchdb-md5': 'react-native-pouchdb-md5',
         'pouchdb-binary-utils':
           '@craftzdog/pouchdb-binary-utils-react-native',
       },
     },
   ],
 ],
};
Enter fullscreen mode Exit fullscreen mode

Hacking PouchDB (Again)

You can check out what changes have been added to the fork.

React Native has been evolving these 2 years, but FileReader.readAsArrayBuffer is still not implemented.
Creating blobs from ArrayBuffer or ArrayBufferView is not supported as well. I tried to make RN support them by looking into their source code but found that it's hard by design.
So, to deal with attachments in your databases, you have to avoid calling these APIs in PouchDB.

So, you have to hack pouchdb-binary-utils as following.
blobOrBufferToBinaryString calls FileReader.readAsArrayBuffer. Call FileReader.readAsDataURL instead:

--- a/packages/node_modules/pouchdb-binary-utils/src/blobOrBufferToBase64-browser.js
+++ b/packages/node_modules/pouchdb-binary-utils/src/blobOrBufferToBase64-browser.js
@@ -1,10 +1,16 @@
-import { btoa } from './base64';
-import blobOrBufferToBinaryString from './blobOrBufferToBinaryString';
-
 function blobToBase64(blobOrBuffer, callback) {
-  blobOrBufferToBinaryString(blobOrBuffer, function (base64) {
-    callback(btoa(base64));
-  });
+  if (blobOrBuffer.size) {
+    var reader = new FileReader();
+    reader.onloadend = function (e) {
+      const text = e.target.result || '';
+      const base64 = text.split(',')[1];
+      callback(base64);
+    };
+    reader.readAsDataURL(blobOrBuffer);
+  } else {
+    const base64 = blobOrBuffer.toString('base64');
+    setImmediate(() => callback(base64));
+  }
 }
Enter fullscreen mode Exit fullscreen mode

In readAsBinaryString, use blobToBase64 instead of readAsArrayBuffer:

--- a/packages/node_modules/pouchdb-binary-utils/src/readAsBinaryString.js
+++ b/packages/node_modules/pouchdb-binary-utils/src/readAsBinaryString.js
@@ -1,32 +1,10 @@
-//Can't find original post, but this is close
-//http://stackoverflow.com/questions/6965107/ (continues on next line)
-//converting-between-strings-and-arraybuffers
-function arrayBufferToBinaryString(buffer) {
-  var binary = '';
-  var bytes = new Uint8Array(buffer);
-  var length = bytes.byteLength;
-  for (var i = 0; i < length; i++) {
-    binary += String.fromCharCode(bytes[i]);
-  }
-  return binary;
-}
+import blobToBase64 from './blobOrBufferToBase64';

-// shim for browsers that don't support it
 function readAsBinaryString(blob, callback) {
-  var reader = new FileReader();
-  var hasBinaryString = typeof reader.readAsBinaryString === 'function';
-  reader.onloadend = function (e) {
-    var result = e.target.result || '';
-    if (hasBinaryString) {
-      return callback(result);
-    }
-    callback(arrayBufferToBinaryString(result));
-  };
-  if (hasBinaryString) {
-    reader.readAsBinaryString(blob);
-  } else {
-    reader.readAsArrayBuffer(blob);
-  }
+  blobToBase64(blob, (base64) => {
+    const binaryString = atob(base64);
+    callback(binaryString);
+  });
 }

 export default readAsBinaryString;
Enter fullscreen mode Exit fullscreen mode

PouchDB tries to use Blob but it does not work as expected due to lack of Blob support in RN. So, we have to deal with attachments always in base64:

-------- a/packages/node_modules/pouchdb-core/src/adapter.js
+++ b/packages/node_modules/pouchdb-core/src/adapter.js
@@ -720,7 +720,7 @@ AbstractPouchDB.prototype.getAttachment =
     }
     if (res.doc._attachments && res.doc._attachments[attachmentId]) {
       opts.ctx = res.ctx;
-      opts.binary = true;
+      opts.binary = false;
       self._getAttachment(docId, attachmentId,
                           res.doc._attachments[attachmentId], opts, callback);
     } else {
Enter fullscreen mode Exit fullscreen mode

pouchdb-md5 also calls readAsArrayBuffer.
I created react-native-pouchdb-md5 which natively calculates MD5 hashes.

Hope you find it useful and helpful.


Subscribe Newsletter

My YouTube channel

Top comments (2)

Collapse
 
freddyfernando93 profile image
Freddy Romero

Hi Takuya, thank you very much for your contribution really, excellent work. I imagine that many are in the same situation. I am trying to implement this approach using Expo. Everything works ok but since i need to use sqlite adapter and i am using expo SQLite (docs.expo.io/versions/latest/sdk/s...) i get invalid adapter error. Do you know how I could solve this issue without having to do Expo eject to use react-native-sqlite-2 ?

Collapse
 
craftzdog profile image
Takuya Matsuyama

you need websql-compatible database.