We’ll integrate the native Folio Reader Android SDK in a Flutter project using the Method Channel.
For the purpose of implementing various functionality in your Flutter project, Flutter offers various dependencies. However, some libraries might not be offered by Flutter or might not be of the same caliber as native SDKs. Therefore, we incorporate native SDKs into the Flutter project.
Flutter offers a variety of libraries for opening epub files, this one meets my needs.
Additionally, you may read more about Mobikul’s Flutter app development services.
Let’s now look at how to incorporate native SDK into a Flutter app.
Create New Flutter App name – Flutter With Folio Reader.
Main.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: true, title: 'Folio Reader With Flutter', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } } |
HomePage.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { //Method Channel var methodChannel = const MethodChannel("folio_reader_sdk"); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Folio Reader with Flutter"), ), body: Center( child: TextButton( onPressed: _openSdk, child: const Text("Open Folio Reader"), ), ), ); } void _openSdk() async { try { await methodChannel.invokeMethod("openFolioSDK", ""); } on PlatformException catch (e) { debugPrint("Failed to Invoke: '${e.message}'."); } } } |
Why Do We Require Method Channel?
- One of the biggest challenges for mobile cross-platform frameworks is how to achieve native performance
- how to help developers create different kinds of features for different devices and platforms with as little effort as possible.
- In doing so, we need to keep in mind that UX should remain the same but with unique components that are specific for each particular platform (Android and iOS).
To Know about Method Channel read this blog.
We have added an ebook for extension .epub in our flutter project assets. Now, we will pass this ebook path in our method as an argument & will use this on our android side.
Folio Reader Android In Flutter, Let’s work on Android side Implementation:
Add folio reader SDK in your Android folder.
https://github.com/FolioReader/FolioReader-Android
Let’s work on our MainActivity file.
Add this in your Settings.gradle file.
1 |
include ':folioreader' |
App Level build.gradle file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' } def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { namespace "com.example.flutter_with_folio_reader" compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "com.example.flutter_with_folio_reader" minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation group: 'commons-io', name: 'commons-io', version: '2.7' implementation project(':folioreader') implementation "com.fasterxml.jackson.core:jackson-core:2.9.7" implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.7" implementation "com.fasterxml.jackson.core:jackson-databind:2.9.7" implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7" //progressBar error solve configurations.matching { it.name == '_internal_aapt2_binary' }.all { config -> config.resolutionStrategy.eachDependency { details -> details.useVersion("3.3.2-5309881") } } implementation 'androidx.core:core-ktx:1.7.0' implementation("com.github.codetoart:r2-shared-kotlin:1.0.4-2") { changing = true } } |
Now Works in folio reader level folioreader/build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
plugins { id 'com.android.library' id 'kotlin-android' id 'kotlin-android-extensions' id 'kotlin-kapt' } android { useLibrary 'org.apache.http.legacy' compileSdkVersion 33 defaultConfig { minSdkVersion 21 targetSdkVersion 33 versionCode 1 versionName "1.0" vectorDrawables.useSupportLibrary = true } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src/main/java'] res.srcDirs = ['res'] } test { java.srcDirs = ['src/test/java'] } } packagingOptions { exclude 'META-INF/ASL2.0' exclude 'META-INF/DEPENDENCIES.txt' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/notice.txt' exclude 'META-INF/license.txt' exclude 'META-INF/dependencies.txt' exclude 'META-INF/LGPL2.1' exclude 'META-INF/services/javax.annotation.processing.Processor' } lintOptions { abortOnError false lintConfig file("lint.xml") } // checkstyle { // ignoreFailures = true // } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.recyclerview:recyclerview:1.1.0" implementation 'com.google.android.material:material:1.6.0' testImplementation 'junit:junit:4.12' implementation 'org.slf4j:slf4j-android:1.7.25' implementation 'com.daimajia.swipelayout:library:1.2.0@aar' //Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10" implementation 'org.greenrobot:eventbus:3.1.1' implementation "com.fasterxml.jackson.core:jackson-core:2.9.7" implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.7" implementation "com.fasterxml.jackson.core:jackson-databind:2.9.7" implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7" implementation "com.google.code.gson:gson:2.8.5" implementation "com.squareup.retrofit2:retrofit:2.5.0" implementation "com.squareup.retrofit2:converter-jackson:2.5.0" implementation "com.squareup.retrofit2:converter-gson:2.5.0" // R2 modules implementation("com.github.codetoart:r2-shared-kotlin:1.0.4-2") { changing = true } implementation("com.github.codetoart:r2-streamer-kotlin:1.0.4-2") { exclude group: "org.slf4j", module: "slf4j-api" changing = true } // Only ReflectionUtils in Spring framework is used implementation 'org.springframework:spring-core:4.3.19.RELEASE' // Lifecycle implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" kapt 'com.android.databinding:compiler:3.1.4' implementation 'com.github.bumptech.glide:glide:4.12.0' // Retrofit api "com.squareup.retrofit2:retrofit:2.9.0" api "com.squareup.retrofit2:adapter-rxjava2:2.9.0" api "com.squareup.okhttp3:logging-interceptor:4.9.1" api "com.squareup.okhttp3:okhttp-urlconnection:4.9.1" // Reactive X android api "io.reactivex.rxjava2:rxandroid:2.1.1" // Because RxAndroid releases are few and far between, it is recommended you also explicitly depend on RxJava's latest version for bug fixes and new features. api "io.reactivex.rxjava2:rxjava:2.2.6" } |
MainActivity of Flutter project
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
class MainActivity : FlutterActivity(), OnHighlightListener, ReadLocatorListener, FolioReader.OnClosedListener { private val LOG_TAG: String = MainActivity::class.java.getSimpleName() private var folioReader: FolioReader? = null private val CHANNEL = "folio_reader_sdk" var methodChannelResult: MethodChannel.Result? = null var flutterEngineInstance: FlutterEngine? = null lateinit var orderId: String @Override override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) folioReader = FolioReader.get() .setOnHighlightListener(this) .setReadLocatorListener(this) .setOnClosedListener(this) getHighlightsAndSave() flutterEngineInstance = flutterEngine MethodChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL ).setMethodCallHandler { call, result -> try { methodChannelResult = result if (call.method.equals("openFolioSDK")) { Log.d("TAG", "Arguments: " + call.arguments) if (call.arguments != null) { OpenFolio(call.arguments as String) } } else { result.notImplemented() } } catch (e: Exception) { Log.d("TAG", e.toString()) } } } fun OpenFolio(url: String) { val readLocator = getLastReadLocator() var config = getSavedConfig(applicationContext) if (config == null) config = Config() config.allowedDirection = Config.AllowedDirection.VERTICAL_AND_HORIZONTAL folioReader?.setReadLocator(readLocator)?.setConfig(config, true)?.openBook(url, "true") } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) } private fun getLastReadLocator(): ReadLocator? { val jsonString = loadAssetTextAsString("Locators/LastReadLocators/last_read_locator_1.json") return fromJson(jsonString) } override fun saveReadLocator(readLocator: ReadLocator) { Log.i( LOG_TAG, "-> saveReadLocator -> " + readLocator.toJson() ) } /* * For testing purpose, we are getting dummy highlights from asset. But you can get highlights from your server * On success, you can save highlights to FolioReader DB. */ private fun getHighlightsAndSave() { Thread { var highlightList: ArrayList<HighLight?>? = null val objectMapper = ObjectMapper() try { highlightList = objectMapper.readValue( loadAssetTextAsString("highlights/highlights_data.json"), object : TypeReference<List<HighLight?>?>() {}) } catch (e: IOException) { e.printStackTrace() } if (highlightList == null) { folioReader?.saveReceivedHighLights(highlightList, OnSaveHighlight { //You can do anything on successful saving highlight list }) } }.start() } private fun loadAssetTextAsString(name: String): String? { var `in`: BufferedReader? = null try { val buf = StringBuilder() val `is` = assets.open(name) `in` = BufferedReader(InputStreamReader(`is`)) var str: String? var isFirst = true while (`in`.readLine().also { str = it } != null) { if (isFirst) isFirst = false else buf.append('\n') buf.append(str) } return buf.toString() } catch (e: IOException) { Log.e("HomeActivity", "Error opening asset $name") } finally { if (`in` != null) { try { `in`.close() } catch (e: IOException) { Log.e("HomeActivity", "Error closing asset $name") } } } return null } override fun onDestroy() { super.onDestroy() FolioReader.clear() } override fun onHighlight(highlight: HighLight, type: HighLightAction) { } override fun onFolioReaderClosed() { TODO("Not yet implemented") } } |
We have fetched the path of the asset in android & used it to open our book using the Folio Reader Android In Flutter using android webview.
That’s all for this Article 🎊 .
Conclusion
We’ve Integrated Folio Reader Android In Flutter In this blog, we’ve learned how to call native SDK In Flutter.
Thanks for reading this blog. You can also check other blogs from here for more knowledge.