Introduction
In the world of blockchain, creating decentralized applications (dApps) that are not only secure and transparent but also user-friendly, is crucial for mass adoption. Celo, with its mobile-first approach, is at the forefront of this shift, offering access to global financial services with just a phone number. What’s even more exciting is that it’s Ethereum-compatible! In this tutorial, we’ll harness the power of Celo and Flutter, a popular mobile development framework, to interact with Celo smart contracts, bringing blockchain capabilities directly to mobile devices.
Prerequisites
Before diving into this tutorial, it’s necessary to have some foundational knowledge and resources:
- Basic understanding of blockchain technology, particularly Ethereum and Celo.
- Familiarity with Flutter and Dart programming languages.
- Basic understanding of solidity.
Requirements
Before we dive into the coding part of this tutorial, there are a few technologies we need to have installed:
- Flutter: We will be using Flutter for our mobile application development. Ensure that it’s installed and properly set up on your system.
- Dart: Since Flutter apps are written in Dart, you must have Dart SDK installed.
- MetaMask: We’ll need MetaMask for interacting with the Celo network.
Now that we have the requirements out of the way, let’s set up a Celo wallet and dive into creating our Flutter application!
Getting Started
Setting Up a Celo Wallet
To interact with a Celo network, we’ll need to create and configure a Celo wallet using Metamask. Follow these steps to create a new Celo wallet:
- Install the Metamask browser extension and create a new wallet.
- Click on the network dropdown in the Metamask window and select “Add network”.
- Click on “Add network manually” at the bottom of your screen
- Enter the following details for the Celo testnet:
- Network Name: Celo Alfajores Testnet
- New RPC URL: https://alfajores-forno.celo-testnet.org
- Chain ID: 44787
- Symbol: cUSD
- Block Explorer URL: https://alfajores-blockscout.celo-testnet.org/
- Click “Save” to add the Celo testnet to your Metamask network list.
Make sure to fund your account with some testnet CELO and cUSD tokens from the Celo Testnet Faucet.
Setting Up the Flutter Development Environment
First, we need to ensure our Flutter development environment is set up and ready. If you’ve not yet installed Flutter, head over to the official Flutter installation guide and follow the instructions for your specific operating system. Once Flutter is installed, verify the installation by running the following command in your terminal:
flutter doctor
This command checks your environment and displays a report of the status of your Flutter installation. Ideally, you want to have no issues in this report, but if there are any, you might need to resolve them before proceeding.
Setting Up a New Flutter Project
Next, let’s create a new Flutter project. Open your terminal, navigate to where you want to store the project, and run the following command:
flutter create celo_flutter_dapp
You should have an output of this form:
This command creates a new Flutter project called celo_flutter_dapp
.
Navigate into the new directory:
cd celo_flutter_dapp
Next, open the project in your preferred text editor or IDE.
Adding Dependencies
For our Flutter app to interact with the Celo blockchain, we need to add some dependencies to our pubspec.yaml
file. The key library we’ll be using is web3dart
, a Dart library that connects our Flutter app to the Ethereum blockchain (which also works for Celo since it’s Ethereum-compatible). We’ll also add http
for handling network requests.
Open your terminal and run the following commands from the root directory of your project
flutter pub add web3dart
flutter pub add http
Your dependencies section in your pubspec.yaml file should look something like this:
With these dependencies added, your Flutter application is now ready to interact with the Celo blockchain.
Creating and Deploying a Celo Smart Contract
Before we interact with a smart contract, we need to create and deploy one. We’ll be using Remix IDE, an online Ethereum IDE that lets us write Solidity smart contracts, for this purpose.
Navigate to Remix Ethereum IDE.
Go to the “File Explorers” tab, and click on the second icon from the left to open the file explorer. Right-click on the contracts
directory button to create a new file and name it HelloWorld.sol.
In the HelloWorld.sol
file, write the following Solidity contract code:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract HelloWorld {
string public greet = "Hello, Celo World!";
function setGreet(string memory _greet) public {
greet = _greet;
}
function getGreet() public view returns (string memory) {
return greet;
}
}
In this contract, we have a public state variable greet
that’s initially set to “Hello, Celo World!”. We have a function setGreet
that allows us to change the greet message and a function getGreet
to fetch the current greet message.
Now, let’s compile the contract. Go to the “Solidity Compiler” tab (fourth icon from the left), select the correct compiler version (0.7.0 in our case), and click on “Compile HelloWorld.sol”.
After successful compilation ( The solidity compiler icon should show a check sign), go to the “Deploy & run transactions” tab (the one with the icon shown below).
Ensure that the Environment is set to “Injected Provider - MetaMask” as shown below. This connects Remix to your Metamask Extension Wallet. Select the HelloWorld contract and click on “Deploy”.
A pop-up from Celo Extension Wallet will open asking you to confirm the transaction.
Confirm it and wait for the transaction to be mined.
Once the transaction is mined, your deployed contract will be available under “Deployed Contracts” as shown below.
You can copy the contract address of the deployed contract by clicking on the copy icon under the Deployed Contract
section. Keep the contract address handy, we’ll need it for our Flutter application.
Interacting with the Smart Contract in Flutter
We’ll now start interacting with the Celo network. For this, we need to establish a connection to the Celo node. Let’s create a new Dart file called celo.dart
inside a new folder named services
in the lib
directory.
In the celo.dart
file, write
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
import 'package:web3dart/credentials.dart';
class Celo {
late Web3Client _client;
Celo() {
Client httpClient = Client();
_client = new Web3Client('https://alfajores-forno.celo-testnet.org', httpClient);
}
}
In the above code, we create a Celo
class with a Web3Client
instance which is responsible for connecting to the Celo network. We are using the Celo Alfajores Testnet URL
for this tutorial.
Now we need the ABI (Application Binary Interface) of the deployed contract to interact with it. The ABI is a JSON representation of our smart contract, which lets the contract’s methods be called in a format the Celo node can understand. Go back to Remix IDE, select the contract, go to the “Solidity compiler” tab, and under the “Contract” dropdown, click on the ABI button to copy the ABI to your clipboard.
Create a new file named celo.abi.json
in the services
folder and paste the copied ABI into it
In your lib/services/celo.dart
file, add the ABI directory to the top of the class, contract address, and credentials as follows:
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
const abiDirectory = 'lib/services/celo.abi.json';
class Celo {
late Web3Client _client;
Celo() {
Client httpClient = Client();
_client = new Web3Client('https://alfajores-forno.celo-testnet.org', httpClient);
}
// Add these lines:
final EthereumAddress _contractAddress = EthereumAddress.fromHex('<paste-the-contract-address-here>');
final credentials = EthPrivateKey.fromHex('<paste-your-private-key-here>');
//...
}
Make sure to replace <paste-the-contract-address-here>
with your contract’s address and <paste-your-private-key-here>
with the private key of the Alfajores wallet you created earlier. Also, we need to declare celo.abi.json
as an asset in our pubspec.yaml
file as shown below:
Reading from the Contract
To read the greet state from our contract, we’ll create a new method readGreet:
Future<String> readGreet() async {
final contractABI = await rootBundle.loadString(abiDirectory);
final contract = DeployedContract(
ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);
final function = contract.function('getGreet');
final response = await _client.call(
contract: contract,
function: function,
params: [],
);
return response[0].toString();
}
Below is the breakdown of the readGreet
function:
Future<String> readGreet() async {
final contractABI = await rootBundle.loadString(abiDirectory);
This function reads the greeting stored in our smart contract. It first loads the contract’s ABI from the directory specified earlier.
final contract = DeployedContract(
ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);
Here, we created a new DeployedContract
instance using the ABI and the contract address. This object represents our deployed contract and lets us interact with it.
final function = contract.function('getGreet');
This line gets the function getGreet
from our contract.
final response = await _client.call(
contract: contract,
function: function,
params: [],
);
This line sends a call to the getGreet
function of our contract. The call method is used for read-only operations in the contract.
return response[0].toString();
Finally, the function returns the greeting obtained from the smart contract.
Writing to the Contract
To change the greet message, we’ll create another method writeGreet:
Future<String> writeGreet(String message) async {
final contractABI = await rootBundle.loadString(abiDirectory);
final contract = DeployedContract(
ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);
final function = contract.function('setGreet');
final response = await _client.sendTransaction(
credentials,
Transaction.callContract(
contract: contract,
function: function,
parameters: [message],
),
chainId: 44787,
);
return response;
}
The writeGreet
function follows a similar pattern to readGreet
, but instead of reading the greeting, it modifies it. It uses the sendTransaction
method, which sends a transaction to the blockchain and modifies its state, unlike the call
method used in readGreet
.
That’s it! You can now interact with your Celo smart contract from your Flutter application.
Setting up the User Interface
Before we can display the greet message or change it, we’ll need to set up the user interface for our Flutter application.
Create a new file home_screen.dart in your lib folder and paste the following code to create a basic layout:
import 'package:celo_flutter_dapp/services/celo.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Celo Flutter Dapp'),
),
// More code will go here
);
}
}
This will create a new screen with an app bar titled Celo Flutter Dapp
.
Now, we need to add some logic and user interface elements to interact with the smart contract. Add the following code inside your _HomeScreenState class:
final _greetController = TextEditingController();
final _celo = Celo();
@override
void initState() {
super.initState();
_updateGreetMessage();
}
void _updateGreetMessage() async {
final message = await _celo.readGreet();
setState(() {
_greetController.text = message;
});
}
void _changeGreetMessage() async {
await _celo.writeGreet(_greetController.text);
_updateGreetMessage();
}
This creates a TextEditingController
to handle the input for the greet message and an instance of our Celo
class. When the screen is initialized (initState
), we read the greet message from our contract and display it in a TextField. The _updateGreetMessage
method reads the greet message from the contract and updates the TextField, while _changeGreetMessage
changes the greet message in the contract and then updates the TextField.
Finally, we add the TextField
and a button to our build method to enable the user to change the greet message:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Celo Flutter Dapp'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
TextField(
controller: _greetController,
decoration: const InputDecoration(
labelText: 'Greet Message',
),
),
ElevatedButton(
onPressed: _changeGreetMessage,
child: const Text('Change Greet Message'),
),
],
),
),
);
}
Also, you would need to make changes to your main.dart
file to ensure that HomeScreen
is the screen that gets displayed when your app runs.
In your lib/main.dart
file, make sure that you’re pointing to the HomeScreen
class as your home route in the MaterialApp
widget. Replace the code in your main.dart
file with the code below:
import 'package:celo_flutter_dapp/home_screen.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}
This main.dart
file sets up a new MaterialApp
and makes the HomeScreen
the default route when the app starts.
Now you have a complete Flutter application that interacts with your Celo smart contract! The TextField displays the current greet message and lets the user input a new one, and the RaisedButton changes the greet message in the contract when pressed.
When you debug the application to your emulator or device, you should have the layout below:
Conclusion
Congratulations! You’ve made it through this comprehensive tutorial on interacting with Celo smart contracts using Flutter. By now, you have gained the knowledge to set up a Flutter development environment, create and deploy a smart contract on the Celo network, and use web3dart to interact with the contract.
This knowledge equips you with the fundamentals to building powerful decentralized applications (DApps) on the Celo platform, extending their reach to mobile devices and improving user experience.
Next Steps
Building on what you’ve learned here, you can start exploring more complex smart contract interactions and dive deeper into the world of decentralized application development. Here are a few suggestions for your continued learning:
- Experiment with different types of smart contracts, perhaps a voting system or a decentralized marketplace.
- Delve deeper into the web3dart library and explore its various functionalities.
- Develop a more interactive and feature-rich user interface for your Flutter application.
- Explore the other components of the Celo ecosystem, like the Celo Wallet or the Celo Dollar (cUSD) stablecoin.
About the Author
I am a Flutter developer, a technical writer, and an ardent blockchain enthusiast. I enjoy transforming complex ideas into user-friendly content and applications. Connect with me on GitHub.
References
With that, we’ve reached the end of this tutorial. I hope it was a beneficial learning experience for you!