Cross-Platform Development with Flutter Rust Bridge
Cross-platform progress lets creators establish empowering sets that function on various operating systems utilizing one single code base. This helps save time and resources since it eliminates the need for independent codes for different platforms. This article will explain using the Flutter Rust Bridge package to simplify cross-platform development.
Discover how at OpenReplay.com.
The most prominent framework used for cross-platform development is Flutter because it uses the Dart programming language to develop compiled mobile applications and web and desktop applications from just one source code. Besides Flutter, Rust is acknowledged for its performance and safety, especially in systems programming and applications needing concurrent execution. You can improve Flutter app functionality by using Rust. This has been made possible by the Flutter Rust Bridge (FRB), which eases interaction between Flutter (Dart) and Rust.
This undertaking aims to create a basic battery testing application that showcases the effectiveness of cross-platform programming using Flutter and Rust together. This software will collect and present the power available to a device’s battery and its current charging status. We intend to develop a quick and efficient app by taking advantage of the flexible UI that Flutter offers and Rust’s reliable performance. This journey will take us through each procedure, from preparing the development environment to creating and integrating the Rust backend with the Flutter front end.
Prerequisites
Before we start creating our battery test app, it’s worth preparing the development environment and guaranteeing we have all the essential devices and technology. If you want to follow this project closely, you should be familiar with Flutter and Rust. On one side, Flutter is a UI toolkit used to build applications for mobile phones, web, and desktop platforms, all from one codebase. On the other hand, Rust is a system programming language that allows writing concurrent programs without sacrificing safety or performance.
You want to work on this project using the Flutter SDK, Rust programming language, and Flutter Rust Bridge (FRB). To develop the cross-platform front end, you will need the Flutter SDK. About backend logic, Rust can do it better. The FRB allows one to communicate between Flutter and Rust using all the advantages of functionality and safety within a Flutter application through the Rust language.
Setting up a Flutter-and-Rust Environment
The initial move is to install the Flutter SDK, which can be obtained from the official website. After downloading, follow the installation instructions given for your operating system. Once the SDK has been installed, set up your development environment using Visual Studio Code or Android Studio as Integrated Development Environments (IDEs). They provide excellent support for creating applications using Flutter. To check whether it has been correctly installed on your system, type this command in your terminal:
flutter doctor
This command evaluates the condition of your environment and generates a report on the current state of your installation.
The following step involves installing Rust from the official Rust website. This website provides an uncomplicated installation script suitable for several operating systems. After completing the installation, set up your Rust environment by obeying the instructions. To confirm that you have installed it properly, run it on your terminal:
rustc --version
This command can ascertain that Rust is properly installed and can be accessed through your command-line interface.
Since you have set up Flutter alongside Rust evaluation settings, it is time to create a Flutter project. Open your command window and execute the command:
flutter create battery_test_app
The command generates a new Flutter project comprising all necessary directories and files for its operation system. When the project is generated, go to your project folder by running on your command prompt screen:
cd battery_test_app
These are just some of the basic steps that should be taken to create an operational environment for developing your application that will test batteries. This preparation is important since it provides you with every tool and setting, which ensures that integrating Rust into Flutter and using the Flutter Rust Bridge work seamlessly.
Building the Rust Backend
Since we are making a battery testing application, we must create its backend using Rust. In this section, we will show you how to configure the Rust project, add the battery status functionality, and prepare the Rust library for connecting with the Flutter front end.
Creating the Rust Library
First, create a new Rust library. Using the terminal, move to a convenient folder before typing the command.
cargo new --lib battery_test_lib
A new Rust library project containing all the required files and folder structure will be generated by this command. Move into the created folder using the following:
cd battery_test_lib
Next, the Cargo.toml
file is where you need to put some dependencies that would help retrieve battery information. For instance, you can use the battery crate to enable a cross-platform API to query the battery state. Just include this line in your [dependencies]
section located in your Cargo.toml
file to add it:
battery = "0.7"
This command tells you that you are using a particular version, called version 0.7, of the battery crate.
Implementing the Battery Status Functionality
Go ahead and open the src/lib.rs
file. This is where you’ll describe how the library will work. You must create functions that obtain battery level and charging status with the aid of a battery crate. A sample implementation follows:
use battery::Manager;
use battery::Result;
pub fn get_battery_status() -> Result<(i32, String)> {
let manager = Manager::new()?;
let batteries = manager.batteries()?;
let battery = batteries.peekable().next().unwrap()?;
let level = (battery.state_of_charge().value * 100.0) as i32;
let status = format!("{:?}", battery.state());
Ok((level, status))
}
Here is the code for battery status in the Rust programming language. A method named get_battery_status
creates an instance of Battery, then gets the first available one and reads its current SoC (state of charge) level and status. While the battery level returns a percentage, the status returns a string
. The return
value of this function is a tuple
containing the integer battery level and the string charge state.
Compiling and exporting
For the Flutter front end to use functions from the Rust language, it is important first to create a Rust library and then generate bindings with the help of the Flutter Rust Bridge (FRB). To begin with, run the command that will compile the library in release mode, creating optimized binary files:
cargo build --release
Locate the output file in the target/release
folder.
The next step is adding the Flutter Rust Bridge dependency to your Rust project. For this, add FRB to your Cargo.toml
file:
[dependencies]
flutter_rust_bridge = "0.5"
Modify your src/lib.rs
file using the required annotations and imports to export Rust functions by utilizing FRB:
use flutter_rust_bridge::RustToDart;
use battery::Result;
#[derive(RustToDart)]
pub struct BatteryStatus {
level: i32,
status: String,
}
pub fn get_battery_status() -> Result<BatteryStatus> {
let manager = Manager::new()?;
let batteries = manager.batteries()?;
let battery = batteries.peekable().next().unwrap()?;
let level = (battery.state_of_charge().value * 100.0) as i32;
let status = format!("{:?}", battery.state());
Ok(BatteryStatus { level, status })
}
The rust code offered works with the flutter_rust_bridge
crate to allow exposure of rust functions in a Flutter app’s interface. To begin with, we need to import all relevant crates and create a struct BatteryStatus
that will store an integer battery level and a string for the charging state. This structure is annotated with RustToDart
; hence, it can be used by any code generation utility for flutter_rust_bridge
.
A result containing a BatteryStatus
struct is returned next by the get_battery_status
function. Within this function, a battery manager is first initialized, and then gets the available battery cache. The battery level percentage is included in the level field, while the status field incorporates a string-formatted charging status. In the end, this function returns a BatteryStatus
struct with adjusted values; hence, get_battery_status
can be called from the Flutter application, giving structured information about your device’s energy source.
To create bindings, run the FRB tool and type in your terminal:
flutter_rust_bridge_codegen
This command will create the appropriate code to connect Flutter and Rust, enabling smooth function calls and data transfers between both environments.
Having implemented the Rust backend in readiness for incorporation, you connect it to the Flutter front end. This arrangement allows you to take advantage of Rust’s performance and safety features within a cross-platform Flutter application, thus providing a sturdy base for the battery test app.
Integrating Rust with Flutter
Connecting Rust and Flutter consists of arranging the Flutter Rust Bridge (FRB) so that they can interact easily. This part will walk you through linking the compiled Rust library with the Flutter project, generating required bindings using FRB, and demonstrating how to call functions written in Rust from the web application UI.
Setting up the bridge
Start by linking the constructed Rust library to your Flutter project. Rust libraries have been generated during previous sessions and placed into the target/release
folder. Hence, they need to be reachable from your Flutter project.
The subsequent step involves establishing a new directory for compiled Rust libraries inside the Flutter project directory. You may, for example, create a folder called rust_libs
.
mkdir rust_libs
Take the compiled Rust libraries in the target/release
folder and place them in the rust_libs
folder. The specific files that need to be copied will depend on what platform you’re aiming for (libbattery_test_lib.so -
for Linux, libbattery_test_lib.dylib –
for macOS, battery_test_lib.dll -
for Windows).
Your Flutter project is now prepared to use these library files by making the necessary adjustments. Make changes to the android/app/build.gradle
file by adding this in the Android
section:
sourceSets {
main {
jniLibs.srcDirs = ['../rust_libs']
}
}
The Android build has to contain the Rust library files, and its configuration is given to Gradle.
On iOS, you can open ios/Runner.xcodeproj
in Xcode and add the Rust library files to the project. Therefore, right-click on the Runner
folder in the project navigator, choose “Add Files to ‘Runner’…” and then select the Rust library files in the rust_libs
directory.
Generating Bindings Using FRB
You should use the Flutter Rust Bridge (FRB) code generation tool to generate the required bindings for Flutter and Rust communication. First, make sure you have the FRB tool. You can install it using Cargo.
cargo install flutter_rust_bridge_codegen
After that, you can use the FRB tool to produce the bindings. In the directory of your Flutter project, make a new script called build.sh
with this content:
#!/bin/bash
flutter_rust_bridge_codegen \
-r rust_libs/battery_test_lib.h \
-d lib/rust_bridge_generated.dart
Using this script, the tool generates Dart bindings for the Rust library and puts them under the lib/rust_bridge_generated.dart
file. Of course, for the script to be run, it must be made executable by executing:
chmod +x build.sh
Execute the command to produce the bindings.
./build.sh
You can directly execute Rust functions through your Flutter code, as the generated Dart bindings will permit this.
Calling Rust Functions from Flutter
After the bindings, you can use Rust functionalities in your Flutter codes. To import the bindings that you generated, start by opening the lib/main.dart
file within your Flutter project, and then proceed with importing them as demonstrated below:
import 'rust_bridge_generated.dart';
Then, use a method to invoke the get_battery_status
function in Rust and show the outcome within your Flutter application. Here is an example of how this can be achieved:
import 'package:flutter/material.dart';
import 'rust_bridge_generated.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BatteryStatusScreen(),
);
}
}
class BatteryStatusScreen extends StatefulWidget {
@override
_BatteryStatusScreenState createState() => _BatteryStatusScreenState();
}
class _BatteryStatusScreenState extends State<BatteryStatusScreen> {
String batteryStatus = 'Fetching battery status...';
@override
void initState() {
super.initState();
fetchBatteryStatus();
}
Future<void> fetchBatteryStatus() async {
try {
final batteryStatusResult = await api.getBatteryStatus();
setState(() {
batteryStatus = 'Battery Level: ${batteryStatusResult.level}%\n'
'Charging Status: ${batteryStatusResult.status}';
});
} catch (e) {
setState(() {
batteryStatus = 'Failed to fetch battery status: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Battery Status'),
),
body: Center(
child: Text(batteryStatus),
),
);
}
}
The Flutter code here does just that; it creates a simple app showing battery levels from the Rust backend. It imports the needed Flutter material
package and Dart files made for Rust functions. The main
method initiates the app by executing MyApp
, which is an instance of the widget. It consists of StatelessWidget
hence, MaterialApp
has BatteryStatusScreen
as its first screen. StatefulWidget
has brought forth BatteryStatusScreen
, which creates its state via the _BatteryStatusScreenState
class. BatteryStatus
is an empty string stored in this class’s instance variable named batteryStatus
.
The function to get the battery status from the Rust backend is called in the initState
method, which is the function fetchBatteryStatus
. We use this asynchronous fetchBatteryStatus
and api.getBatteryStatus
to get battery status. If the call returns a successful response, then the value obtained by it would be used to update the batteryStatus
variable with the level and charging states corresponding to that value; otherwise, if that call fails, then this variable will be updated with an error message. The build method constructs UI such that there exists an AppBar
labeled “Battery Status,” alongside a Center widget containing a Text widget that shows the current values of the variable in the batteryStatus
state. Such a design will ensure that the user interface reflects whatever has transpired regarding batteries acquired through the Rust backend.
Building the Flutter front-end
Now that our Flutter app has a smoothly functioning Rust backend, it is time to consider a front end. This section will focus on creating a user interface (UI) that displays battery details and communicates with Rust’s backend to obtain live information.
Designing the UI
A good user experience largely hinges on creating a simple and intuitive interface. Flutter’s rich assortment of widgets and tools simplifies designing a responsive UI. Our battery test app should have an interface that displays the battery level and charging status. In your Flutter project, open up lib/main.dart
. As outlined in our previous section, you should already have the necessary imports for the BatteryStatusScreen
. Let’s now consider making modifications to the UI design to improve it. As such, let us add more styled elements to the BatteryStatusScreen
widget:
import 'package:flutter/material.dart';
import 'rust_bridge_generated.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Battery Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BatteryStatusScreen(),
);
}
}
class BatteryStatusScreen extends StatefulWidget {
@override
_BatteryStatusScreenState createState() => _BatteryStatusScreenState();
}
class _BatteryStatusScreenState extends State<BatteryStatusScreen> {
String batteryStatus = 'Fetching battery status...';
@override
void initState() {
super.initState();
fetchBatteryStatus();
}
Future<void> fetchBatteryStatus() async {
try {
final batteryStatusResult = await api.getBatteryStatus();
setState(() {
batteryStatus = 'Battery Level: ${batteryStatusResult.level}%\n'
'Charging Status: ${batteryStatusResult.status}';
});
} catch (e) {
setState(() {
batteryStatus = 'Failed to fetch battery status: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Battery Status'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.battery_full,
size: 100.0,
color: Colors.green,
),
SizedBox(height: 20.0),
Text(
batteryStatus,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20.0),
),
],
),
),
),
);
}
}
This app displays battery status retrieved from Rust’s backend in Flutter code. It starts with importing essential Flutter material
packages and generating dart bindings for Rust functions. The application begins with the main
function, whose instance of the myapp
widget is run to initiate it. The main structure of this app has been laid out by MyApp
, which extends StatelessWidget
. It calls itself a battery test app and uses a dark theme throughout its design. BatteryStatusScreen
is set as the home screen of this device.
The BatteryStatusScreen
widget expands the StatefulWidget
class so that its interface can vary according to the current battery condition on the device. The implementation of its state is found in the _BatteryStatusScreenState
class, which holds a single variable batteryStatus
defined as a constant placeholder string. At the start of this process, in our initState
function, we will invoke the fetchBatteryStatus
function because we want to be able to read from it and thus know if we are running low or not without having to inquire every time; otherwise, we’ll constantly have dead phones! In addition, it also helps to know when we have only a 15% charge remaining, as it could come in handy knowing whether or not it’s worth it to take our PowerBank with us. When this method is successful at obtaining data, the batteryStatus
variable changes to the current level of charge and indicates whether or not the device is charging. When there are issues relaying these types of information back across, however, state
variables become error messages instead.
Interacting with the Rust Backend
You are expected to communicate with Rust’s backend using the Flutter front-end for dynamically changing user interfaces and real-time information regarding battery life. This is achieved through the fetchBatteryStatus
method located within the BatteryStatusScreen
widget, where it calls upon getBatteryStatus
written in Rust, alters the state variable of batteryStatus
, and then executes setState
, which results in updating the UI. It would simplify things for users. The addition of the FloatingActionButton
component to the BatteryStatusScreen
widget provides the option of easily refreshing their battery status through a button.
import 'package:flutter/material.dart';
import 'rust_bridge_generated.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Battery Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BatteryStatusScreen(),
);
}
}
class BatteryStatusScreen extends StatefulWidget {
@override
_BatteryStatusScreenState createState() => _BatteryStatusScreenState();
}
class _BatteryStatusScreenState extends State<BatteryStatusScreen> {
String batteryStatus = 'Fetching battery status...';
@override
void initState() {
super.initState();
fetchBatteryStatus();
}
Future<void> fetchBatteryStatus() async {
try {
final batteryStatusResult = await api.getBatteryStatus();
setState(() {
batteryStatus = 'Battery Level: ${batteryStatusResult.level}%\n'
'Charging Status: ${batteryStatusResult.status}';
});
} catch (e) {
setState(() {
batteryStatus = 'Failed to fetch battery status: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Battery Status'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.battery_full,
size: 100.0,
color: Colors.green,
),
SizedBox(height: 20.0),
Text(
batteryStatus,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20.0),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: fetchBatteryStatus,
tooltip: 'Refresh',
child: Icon(Icons.refresh),
),
);
}
}
This Flutter code establishes a basic app that presents battery status details obtained from a Rust backend. The MyApp
widget is run as an instance in the main
function to start the app by providing a title, theme, and a BatteryStatusScreen
as its home screen. The BatteryStatusScreen
widget is stateful and administered by _BatteryStatusScreenState
. At first, batteryStatus
is set to “Fetching battery status…”. In the initState
method, the fetchBatteryStatus
function retrieves the battery status from the Rust using api.getBatteryStatus
. If the call goes through, it will update the batteryStatus
with the battery level and whether it is charging, but in case of failure, an error message will be shown instead.
UI is constructed in a build method that comprises an AppBar
with the title “Battery Status,” a centered icon widget that depicts the battery, a SizedBox for spacing out different components, and a Text widget displaying the status of the battery. If you want to refresh it manually, the FloatingActionButton
calls the fetchBatteryStatus
method to get the latest information about its current status. How we can keep the UI in sync with live messages about how our power cell is charged depends on the Rust server fetching service.
Running the app
Once Flutter’s interface has been developed and the Rust back-end has been added to it, these are the steps to take to start running the app:
- Build the Rust Code: Before you start the Flutter project, be sure to compile your Rust code and share an output library. You can run the following commands to achieve this:
cargo build --release
- Link the Rust Library: Ensure your Flutter project is linked to the compiled library. Based on the platform you are using (Android or iOS), certain steps are required to integrate the Rust library.
For Android: You should put the assembled Rust library (e.g., libyourlibrary.so
) into the proper folder for your Flutter project, usually found at android/app/src/main/jniLibs/
.
Next, make changes in your android/app/build.gradle
in such a manner that it contains this library:
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
For iOS: Your Flutter project should have the compiled Rust library (for instance, libyourlibrary.a
) in the correct place, generally ios/
.
To link the Rust library, you should update your Xcode project settings as follows:
- Open
ios/Runner.xcworkspace
in Xcode. - Choose your project from the project navigator.
- Select the target for your app.
- Go to the “Build Phases” tab.
- Add the Rust library to “Link Binary With Libraries.”
- Run the app: Open a terminal, navigate to the directory of your Flutter project, and execute:
flutter run
The command builds your Flutter application and runs it on an emulator or connected device. If every configuration is correct, your app should display the battery status information obtained from the Rust backend.
Output:
From the Rust back-end, the Flutter app gets the battery status information. When the app launches, it initially shows a message that it’s fetching battery status. Then, once it has retrieved the battery status, it is displayed through UI updating, which shows such things as “Battery Level: 98%” and “Charging Status: Charging.”
Conclusion
Ultimately, a battery test application that uses both Flutter and Rust is a good demonstration of how two technologies for cross-platform development can be combined. The app combines Flutter’s user interface capabilities with Rust’s performance and system-level access, creating a strong platform for monitoring battery status. Setting up the Flutter environment was the first step of this process. This was followed by configuring Rust and linking the two through a Flutter-Rust Bridge (FRB). We then created a Rust backend that could access our battery status queries connected to the front end of our application, which was built using Fluttering and output information on it in a more user-friendly manner.