Flutter Setup
This tutorial will guide you through integrating the iOS bindings and Android bindings into an Flutter) project. Before you begin, make sure you’ve completed the "Getting Started - 3. Mopro build" process with selecting iOS platform and Android platform and have the MoproiOSBindings
and MoproAndroidBindings
folder ready:
Flutter is a framework for building natively compiled, multi-platform applications from a single codebase.
In this tutorial, you will learn how to create a native Mopro module on both Android and iOS simulators/devices.


In this example, we use Circom circuits and their corresponding .zkey
files. The process is similar for other provers.
0. Prerequisites
-
Install Flutter
If Flutter is not already installed, you can follow the official Flutter installation guide for your operating system.
-
Check Flutter Environment
After installing Flutter, verify that your development environment is properly set up by running the following command in your terminal:
flutter doctor
This command will identify any missing dependencies or required configurations.
-
Install Flutter Dependencies
Navigate to the root directory of the project in your terminal and run:
flutter pub get
This will install the necessary dependencies for the project.
-
Create a Flutter App
If you already have Flutter app, you can skip this step. If you don’t have a one, follow this tutorial to set one up. Or run
flutter create <YOUR_FLUTTER_APP>
1. Creating a Native Module
Create a plugin to integrate Mopro bindings into your project.
flutter create mopro_flutter_plugin -t plugins
To learn more about flutter packages/plugins, please refer to Flutter - Developing packages & plugins
To support both iOS and Android, we need to build native modules for each platform.
Start by adding the necessary platforms to the plugin:
Navigate to the mopro_flutter_plugin
directory
cd mopro_flutter_plugin
and run the following command:
flutter create -t plugin --platforms ios,android .
2. Implement the module on iOS
Please refer to flutter-app to see the latest update.
2-1 Use a framework
-
Get the
MoproiOSBindings
frommopro build
.infoSee Getting Started
-
Place the
MoproiOSBindings/mopro.swift
file tomopro_flutter_plugin/ios/Classes/mopro.swift
Place theMoproiOSBindings/MoproBindings.xcframework
file tomopro_flutter_plugin/ios/MoproBindings.xcframework
.
The structure will look likeflutter-app/
├── ...
└── mopro_flutter_plugin
└── ios/
├── ...
├── Classes/
│ ├── ...
│ └── mopro.swift
└── MoproBindings.xcframework/... -
Bundle the bindings in
mopro_flutter_plugin/ios/mopro_flutter_plugin.podspec
/mopro_flutter_plugin/ios/mopro_flutter_plugin.podspec...
s.source_files = 'Classes/**/*'
s.vendored_frameworks = 'MoproBindings.xcframework'
s.preserve_paths = 'MoproBindings.xcframework/**/*'
...
2-2 Create convertible types for Javascript library with swift.
-
Create these types in the file:
mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swift
/mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swiftclass FlutterG1 {
let x: String
let y: String
let z: String
init(x: String, y: String, z: String) {
self.x = x
self.y = y
self.z = z
}
}
// ...noteSee the full implementation here:
MoproFlutterPlugin.swift
-
Define helper functions to bridge types between the Mopro bindings and the Flutter framework:
/mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swift// convert the mopro proofs to be exposed to Flutter framework
func convertCircomProof(res: CircomProofResult) -> [String: Any] {
let g1a = FlutterG1(x: res.proof.a.x, y: res.proof.a.y, z: res.proof.a.z)
// ...
}
// convert the Flutter proofs to be used in mopro bindings
func convertCircomProofResult(proof: [String: Any]) -> CircomProofResult {
let proofMap = proof["proof"] as! [String: Any]
let aMap = proofMap["a"] as! [String: String]
let g1a = G1(x: aMap["x"] ?? "0", y: aMap["y"] ?? "0", z: aMap["z"] ?? "1")
// ...
}noteSee the full implementation here:
MoproFlutterPlugin.swift
-
Define the native module API. See the Writing custom platform-specific code for details.
public class MoproFlutterPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "mopro_flutter_plugin", binaryMessenger: registrar.messenger())
let instance = MoproFlutterPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "generateCircomProof":
guard let args = call.arguments as? [String: Any],
let zkeyPath = args["zkeyPath"] as? String,
let inputs = args["inputs"] as? String,
let proofLib = args["proofLib"] as? ProofLib
else {
result(FlutterError(code: "ARGUMENT_ERROR", message: "Missing arguments", details: nil))
return
}
do {
// Call the function from mopro.swift
let proofResult = try generateCircomProof(
zkeyPath: zkeyPath, circuitInputs: inputs, proofLib: proofLib)
let resultMap = convertCircomProof(res: proofResult)
// Return the proof and inputs as a map supported by the StandardMethodCodec
result(resultMap)
} catch {
result(
FlutterError(
code: "PROOF_GENERATION_ERROR", message: "Failed to generate proof",
details: error.localizedDescription))
}
}
}
// ...
}
See the full implementation here: MoproFlutterPlugin.swift
3. Implement the module on Android
3-1 Add dependency for jna in the file build.gradle
.
dependencies {
implementation("net.java.dev.jna:jna:5.13.0@aar")
}
3-2 Include Mopro bindings in the native Android module
-
Get the
MoproAndroidBindings
frommopro build
.infoSee Getting Started
-
Move the
jniLibs
directory tomopro_flutter_plugin/android/src/main
.
And moveuniffi
directory tomopro_flutter_plugin/android/src/main/kotlin
.
The folder structure should be as follows:flutter-app/
├── ...
└── mopro_flutter_plugin
└── android/
├── ...
└── src/
├── ...
└── main/
├── ...
├── jniLibs/...
└── kotlin/
├── ...
└── uniffi/mopro/mopro.kt
3-3 Create convertible types for Javascript library with kotlin.
-
Create these types in the file:
mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.kt
/mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.ktclass FlutterG1(x: String, y: String, z: String) {
val x = x
val y = y
val z = z
}
// ...noteSee the full implementation here:
MoproFlutterPlugin.kt
-
Define helper functions to bridge types between the Mopro bindings and the Flutter framework:
/mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.kt// convert the mopro proofs to be exposed to Flutter framework
fun convertCircomProof(res: CircomProofResult): Map<String, Any> {
val g1a = FlutterG1(res.proof.a.x, res.proof.a.y, res.proof.a.z)
// ...
}
// convert the Flutter proofs to be used in mopro bindings
fun convertCircomProofResult(proofResult: Map<String, Any>): CircomProofResult {
val proofMap = proofResult["proof"] as Map<String, Any>
val aMap = proofMap["a"] as Map<String, Any>
val g1a = G1(
aMap["x"] as String,
aMap["y"] as String,
aMap["z"] as String
)
// ...
}noteSee the full implementation here:
MoproFlutterPlugin.kt
-
Define the native module API. See the Writing custom platform-specific code for details.
class MoproFlutterPlugin: FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mopro_flutter_plugin")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "generateCircomProof") {
val zkeyPath = call.argument<String>("zkeyPath") ?: return result.error(
"ARGUMENT_ERROR",
"Missing zkeyPath",
null
)
val inputs =
call.argument<String>("inputs") ?: return result.error(
"ARGUMENT_ERROR",
"Missing inputs",
null
)
val proofLibIndex = call.argument<Int>("proofLib") ?: return result.error(
"ARGUMENT_ERROR",
"Missing proofLib",
null
)
val proofLib = if (proofLibIndex == 0) ProofLib.ARKWORKS else ProofLib.RAPIDSNARK
val res = generateCircomProof(zkeyPath, inputs, proofLib)
val resultMap = convertCircomProof(res)
result.success(resultMap)
// ...
} else {
result.notImplemented()
}
}
// ...
}
See the full implementation here: MoproFlutterPlugin.kt
4. Define Dart APIs
Please refer to flutter-app to see the latest update.
-
Define the types for the native module. Add the following types in the file
mopro_flutter_plugin/lib/mopro_types.dart
:/mopro_flutter_plugin/lib/mopro_types.dartimport 'dart:typed_data';
class G1Point {
final String x;
final String y;
final String z;
G1Point(this.x, this.y, this.z);
String toString() {
return "G1Point(\nx: $x, \ny: $y, \nz: $z)";
}
}
enum ProofLib { arkworks, rapidsnark }
// ...noteSee the full implementation here:
mopro_types.dart
-
Add the native module's API functions in these files.
class MethodChannelMoproFlutterPlugin extends MoproFlutterPluginPlatform {
/// The method channel used to interact with the native platform.
final methodChannel = const MethodChannel('mopro_flutter_plugin');
Future<CircomProofResult?> generateCircomProof(
String zkeyPath, String inputs, ProofLib proofLib) async {
final proofResult = await methodChannel
.invokeMethod<Map<Object?, Object?>>('generateCircomProof', {
'zkeyPath': zkeyPath,
'inputs': inputs,
'proofLib': proofLib.index,
});
if (proofResult == null) {
return null;
}
var circomProofResult = CircomProofResult.fromMap(proofResult);
return circomProofResult;
}
// ...
}
See the full implementation here: mopro_flutter_method_channel.dart
abstract class MoproFlutterPluginPlatform extends PlatformInterface {
//...
Future<CircomProofResult?> generateCircomProof(
String zkeyPath, String inputs, ProofLib proofLib) {
throw UnimplementedError('generateCircomProof() has not been implemented.');
}
//...
}
See the full implementation here: mopro_flutter_platform_interface.dart
class MoproFlutterPlugin {
Future<String> copyAssetToFileSystem(String assetPath) async {
// Load the asset as bytes
final byteData = await rootBundle.load(assetPath);
// Get the app's document directory (or other accessible directory)
final directory = await getApplicationDocumentsDirectory();
//Strip off the initial dirs from the filename
assetPath = assetPath.split('/').last;
final file = File('${directory.path}/$assetPath');
// Write the bytes to a file in the file system
await file.writeAsBytes(byteData.buffer.asUint8List());
return file.path; // Return the file path
}
Future<CircomProofResult?> generateCircomProof(
String zkeyFile, String inputs, ProofLib proofLib) async {
return await copyAssetToFileSystem(zkeyFile).then((path) async {
return await MoproFlutterPlatform.instance
.generateCircomProof(path, inputs, proofLib);
});
}
//...
}
See the full implementation here: mopro_flutter.dart
5. Use the plugin
Follow the steps below to integrate mopro plugin.
-
Add the plugin to
pubspec.yaml
as a dependency:---
dependencies:
flutter:
sdk: flutter
mopro_flutter_plugin:
path: ./mopro_flutter_plugin -
Copy keys in the
assets
folder like this
flutter-app/
├── ...
├── assets/multiplier2_final.zkey
└── lib/main.dartand update
pubspec.yaml
flutter:
assets:
- assets/multiplier2_final.zkey -
Generate proofs in the app
import 'package:mopro_flutter_plugin/mopro_flutter_plugin.dart';
import 'package:mopro_flutter_plugin/mopro_types.dart';
final _moproFlutterPlugin = MoproFlutterPlugin();
var inputs = '{"a":["3"],"b":["5"]}';
var proofResult = await _moproFlutterPlugin.generateCircomProof(
"assets/multiplier2_final.zkey",
inputs,
ProofLib.arkworks
);
6. Customizing the zKey
-
Place your
.zkey
file in your app's assets folder and remove the example fileassets/multiplier2_final.zkey
. If your.zkey
has a different file name, don't forget to update the asset definition in your app'spubspec.yaml
:assets:
- - assets/multiplier2_final.zkey
+ - assets/your_new_zkey_file.zkey -
Load the new
.zkey
file in your Dart code by updating the file path inlib/main.dart
:var inputs = '{"a":["3"],"b":["5"]}';
- proofResult = await _moproFlutterPlugin.generateCircomProof("assets/multiplier2_final.zkey", inputs, ProofLib.arkworks);
+ proofResult = await _moproFlutterPlugin.generateCircomProof("assets/your_new_zkey_file.zkey", inputs, ProofLib.arkworks);
Don't forget to modify the input values for your specific case!
7. What's next
- Update your ZK circuits as needed. After making changes, be sure to run:
This ensures the bindings are regenerated and reflect your latest updates.
mopro build
mopro update - Build your mobile app frontend according to your business logic and user flow.
- Expose additional Rust functionality:
If a function is missing in Swift, Kotlin, React Native, or Flutter, you can:
- Add the required Rust crate in
Cargo.toml
- Annotate your function with
#[uniffi::export]
(See the Rust setup guide for details).
Once exported, the function will be available across all supported platforms.
- Add the required Rust crate in