Posted on / by Bondan Herumurti / in App Development, React Native

Integrating FolioReader and React Native

It has been months for me to find a solution for product our team want to build: ePub Reader, there is one provided by futurepress. But it doesn’t satisfied the requirements.

One of the most popular “epub sdk android ios” keyword searched result is FolioReader. However its development for RN was all “initial commit”, and no one answering the issues about maintenance.

So this is a good chance for us to learn bridging native modules into react native projects.

Before starting i want to state, that this tutorial is NOT

  1. A process of creating NPM module for folioreader, this would be a manual approach to integrate folioreader, but i would love to see if someone would create a full fledge npm package.
  2. Complete tutorial covering all callback from folioreader action method available for React Native component.

this tutorial would cover React Native version 0.59.2 at the moment

In this tutorial we will create a very simple app, with one button that will DOWNLOAD the epub from source and directly open folioreader to read the epub

 

This module will use RNFetchBlob in order to download the files and save it to internal memory storage and folioreader to open the downloaded epub

 

Lets begin from React Native

Lets init the project

react-native init folrn

lets install some dependency, first the RNFetchBlob, you can follow steps ini here

https://github.com/joltup/rn-fetch-blob

or simply

yarn add rn-fetch-blob

or

npm install --save rn-fetch-blob

whatever suits you, on the project, i use yarn for speed.

next, dont forget to link all dependencies needed for RNFetchBlob

RNFB_ANDROID_PERMISSIONS=true react-native link rn-fetch-blob

this should be enough to implement the download.

Lets implement this line on our App.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, TouchableOpacity} from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
import _ from 'lodash';


export default class App extends Component {

  _downloadAndRead = async () => {
    const epubUrl = "https://s3-us-west-2.amazonaws.com/pressbooks-samplefiles/LewisTheme/The-Problems-of-Philosophy-LewisTheme.epub";
    let ext = 'epub';
    try {
      let dirs = RNFetchBlob.fs.dirs;
      const epubName = `${_.random(0, 99999)}.${ext}`;
      const filePath = `${dirs.DocumentDir}/${epubName}`;
      const res = await RNFetchBlob.config({ fileCache: true, appendExt: ext, path: filePath }).fetch('GET', epubUrl);
      const status = res.info().status;
      if (status >= 200 && status < 300) {
        let epubFileLocation = res.path();
        console.log('epubFileLocation', epubFileLocation);
      } else {
        throw(res)
      }
    } catch (error) {
      console.log("ERROR", error);
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={() => this._downloadAndRead()}>
          <View style={{padding: 10, backgroundColor: "#007AFF"}}>
            <Text style={{color: 'white'}}>Download & Read</Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

try run

react-native run-ios

don’t forget to activate JS Debugging to check if our files properly downloaded, if everything goes well, it should be print the path on the chrome console

epubFileLocation /Users/yourcomputer/Library/Developer/CoreSimulator/Devices/96B3402D-CE90-4B7E-AEC6-20FD2273A157/data/Containers/Data/Application/A66C883B-2356-4E62-A181-87C1EE96300F/Documents/62097.epub

something like this, if there is an error take your time to googling the error ūüôā also check on the android side, should be print something like

Now that the file downloaded properly, we can move to native bridging.

 

 

Lets start with iOS platform

First open your ios folder on the project and find folrn.xcodeproj open it with xcode, next make sure you can build and run the project using CMD + B or play button on top left. If there is any error, please resolve before proceed to next steps.

Now lets install the FolioReader sdk through cocoapods. if you’re not familiar with cocoapods then you can checkout

https://cocoapods.org/ for more info on installation

First steps, navigate on your ios folder and execute

pod init

now open the project and cek the Podfile, and add the folioreaderkit to the pod, your podfile should look like this

platform :ios, '9.0'

target 'folrn' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  use_frameworks!

  # Pods for folrn
  pod 'FolioReaderKit'
end

and install the pod using

pod install

it should take a while, get a coffee or a power nap..

close the xcode and open the folrn.xcworkspace for pods installed workspace, and add the FolioReader Kit framework to Linked Frameworks & Binaries

Press play to see, everything still going okay, any errors happened please resolve it first

 

Before dive to more coding, lets see how folioreader for ios will perform

import FolioReaderKit

func open(sender: AnyObject) {
    let config = FolioReaderConfig()
    let bookPath = Bundle.main.path(forResource: "book", ofType: "epub")
    let folioReader = FolioReader()
    folioReader.presentReader(parentViewController: self, withEpubPath: bookPath!, andConfig: config)
}

With this snippets, we are aware folioreader require viewController in order to be able to present its reader. With this requirement, we decided to create a UIViews to be rendered on our app and finally create UIViewController which will be used by folioreader to present its reader.

So First lets find out how to bridging UIViews in order to appear to our app. we are following this tutorial get the concept of bridging UIView to React Native

https://medium.com/@Noitidart/bridging-a-swift-component-from-cocoapods-7ffa04388963

Lets Code!

First, we need to create Objective C in order to bridge the FolioReader from swift, Lets add a cocoa touch class type files to

Name it FolioReaderModule

it will create FolioReaderModule.m and FolioReaderModule.h, these files are the bridging files that will registered the views and also methods that will be exposed to react native. This might be trigger an error said RCTViewManager not found, just replace the content of FolioReaderModule.h  with this

#import <React/RCTViewManager.h>

NS_ASSUME_NONNULL_BEGIN

@interface FolioReaderModule : RCTViewManager

@end

NS_ASSUME_NONNULL_END

Now create a file called FolioReaderView.swift, this is the file that will hold the view that will be imported to our react native project. During the creation, it will prompt to create bridging header, just follow the steps

dont forget to add this snippet to folrn-Bridging-Header.h

#import "React/RCTViewManager.h"

create a simple UIView class on the FolioReaderView.swift

import Foundation

class FolioReaderView : UIView {
  
}

Now lets import a simple view to the project, start with modifying the FolioReaderModule.m, replace the default @interface with

@interface RCT_EXTERN_MODULE(FolioReaderViewManager, RCTViewManager)

@end

Now you may ask, what is FolioReaderViewManager? well chilled out now lets create that file FolioReaderViewManager.swift and place content like this

//
//  FolioReaderViewManager.swift
//  folrn
//
//  Created by Bondan Herumurti on 05/04/19.
//  Copyright © 2019 Facebook. All rights reserved.
//

import Foundation

@objc(FolioReaderViewManager)
class FolioReaderViewManager: RCTViewManager {
  override func view() -> UIView! {
    return FolioReaderView();
  }

  @objc override static func requiresMainQueueSetup() -> Bool {
    return false
  }
}

this is a crucial file which said the View is accessible on react native later using requireNativeComponent(‘FolioReaderView’, null).¬†Now lets create a simple native swift view to check if the view is properly bridged

//
//  FolioReaderView.swift
//  folrn
//
//  Created by Bondan Herumurti on 05/04/19.
//  Copyright © 2019 Facebook. All rights reserved.
//

import Foundation
import UIKit

class FolioReaderView : UIView {
  let label = UILabel();
  private var _txt:String = "This is folioreader placeholder"
  
  override init(frame: CGRect) {
    super.init(frame: frame);
    self.label.text = self._txt
    self.addSubview(self.label)
  }
  
  override func layoutSubviews() {
    super.layoutSubviews()
    self.label.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height);
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

a very simple UIView, lets back to the React Native source code and check if this UIView properly bridged, lets modify App.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, TouchableOpacity, requireNativeComponent} from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
import _ from 'lodash';

const FolioReaderView = requireNativeComponent("FolioReaderView", null);

export default class App extends Component {

  _downloadAndRead = async () => {
    const epubUrl = "https://s3-us-west-2.amazonaws.com/pressbooks-samplefiles/LewisTheme/The-Problems-of-Philosophy-LewisTheme.epub";
    let ext = 'epub';
    try {
      let dirs = RNFetchBlob.fs.dirs;
      const epubName = `${_.random(0, 99999)}.${ext}`;
      const filePath = `${dirs.DocumentDir}/${epubName}`;
      const res = await RNFetchBlob.config({ fileCache: true, appendExt: ext, path: filePath }).fetch('GET', epubUrl);
      const status = res.info().status;
      if (status >= 200 && status < 300) {
        let epubFileLocation = res.path();
        console.log('epubFileLocation', epubFileLocation);
      } else {
        throw(res)
      }
    } catch (error) {
      console.log("ERROR", error);
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <FolioReaderView style={{width: '100%', height: 60, backgroundColor: 'red'}} />
        <TouchableOpacity onPress={() => this._downloadAndRead()}>
          <View style={{padding: 10, backgroundColor: "#007AFF"}}>
            <Text style={{color: 'white'}}>Download & Read</Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

now run it, you should see the simulator like this

well well well, it something

Now lets working on the FolioReaderViewController where FolioReader can present its reader, first lets add FolioReaderViewController.swift file and paste this content

//
//  FolioReaderViewController.swift
//  folrn
//
//  Created by Bondan Herumurti on 05/04/19.
//  Copyright © 2019 Facebook. All rights reserved.
//

import Foundation
import FolioReaderKit

class FolioreaderViewController: UIViewController {
  let folioReader = FolioReader()
  let config = FolioReaderConfig()
  let bookPath = Bundle.main.path(forResource: "ebook", ofType: "epub")
  
  override func viewDidLoad() {
    super.viewDidLoad();
  }
  
  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
  }
  
  func open(path: NSString) {
    folioReader.presentReader(parentViewController: self, withEpubPath: bookPath!, andConfig: config)
  }
}

So here are some things 88

Our plan is to access the func open in order to trigger folioreader to open its reader and this function is actually accessible from the view that we created before

 

well i just run out my time…. to be continue…

 

 

Leave a Reply