Photo by Artur Shamsutdinov on Unsplash
Integrating MetaMask with Your Flutter dApp: A Step-by-Step Guide
Comprehensive Guide to Add MetaMask Support in Flutter dApp
In the world of Web3 development, creating decentralized applications (dApps) that interact with blockchain networks is becoming increasingly popular. One crucial aspect of building dApps is integrating wallet functionality to allow users to interact with smart contracts. In this blog post, we'll explore how to integrate MetaMask, one of the most popular Ethereum wallets, into a Flutter dApp.
Why MetaMask?
MetaMask is a browser extension and mobile app that serves as a crypto wallet and a gateway to blockchain apps. It allows users to store and manage account keys, broadcast transactions, send and receive Ethereum-based cryptocurrencies and tokens, and securely connect to decentralized applications. By integrating MetaMask into your Flutter dApp, you're providing users with a familiar and secure way to interact with your application's blockchain features.
Our Demo: RoomFlow
For this tutorial, we'll be using a simplified version of a dApp called RoomFlow. RoomFlow is a decentralized platform for booking and managing spaces (like rooms or apartments) using smart contracts. We'll focus on the SpaceServices
class, which handles the interaction between our Flutter app and the Ethereum blockchain.
Prerequisites
Before we dive in, make sure you have the following set up:
Flutter development environment
Basic understanding of Dart and Flutter
Familiarity with Ethereum and smart contracts
MetaMask browser extension or mobile app installed
Step 1: Setting Up Dependencies
First, we need to add the necessary dependencies to our pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
web3dart: ^2.3.5
http: ^0.13.4
web_socket_channel: ^2.2.0
web3modal_flutter: ^2.0.0
Run flutter pub get
to install these dependencies.
Step 2: Creating the SpaceServices Class
Now, let's create our SpaceServices
class, which will handle the MetaMask integration and blockchain interactions:
import 'package:flutter/material.dart';
import 'package:web3dart/web3dart.dart';
import 'package:http/http.dart' as http;
import 'package:web_socket_channel/io.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
class SpaceServices extends ChangeNotifier {
late Web3Client web3client;
late W3MService w3mService;
EthereumAddress? _userAddress;
final String _rpcUrl = 'http://localhost:7545'; // Replace with your RPC URL
final String _wsUrl = 'ws://localhost:7545'; // Replace with your WebSocket URL
final int chainId = 1337; // Replace with your chain ID
bool get isConnected => _userAddress != null;
SpaceServices() {
_init();
}
Future<void> _init() async {
w3mService = W3MService(
projectId: 'YOUR_PROJECT_ID', // Replace with your WalletConnect project ID
metadata: const PairingMetadata(
name: 'RoomFlow',
description: 'Decentralized Space Booking',
url: 'https://roomflow.example.com',
icons: ['https://roomflow.example.com/icon.png'],
),
);
try {
await w3mService.init();
} catch (e) {
print("Error initializing W3MService: $e");
}
web3client = Web3Client(_rpcUrl, http.Client(), socketConnector: () {
return IOWebSocketChannel.connect(_wsUrl).cast<String>();
});
// Try to connect to MetaMask
await connectToMetaMask();
}
Future<void> connectToMetaMask() async {
try {
await w3mService.openModal();
final session = w3mService.session;
if (session != null) {
_userAddress = EthereumAddress.fromHex(session.address);
notifyListeners();
}
} catch (e) {
print("Error connecting to MetaMask: $e");
}
}
Future<void> disconnectWallet() async {
await w3mService.disconnect();
_userAddress = null;
notifyListeners();
}
// Example method: Booking a space
Future<void> bookSpace(BigInt spaceId, BigInt startDate, BigInt endDate, BigInt price) async {
if (_userAddress == null) {
throw Exception("Wallet not connected");
}
// This is a simplified example. In a real app, you'd interact with your smart contract here.
final transaction = Transaction(
to: EthereumAddress.fromHex('0x1234...'), // Your contract address
from: _userAddress,
value: EtherAmount.inWei(price),
data: Uint8List.fromList('bookSpace(uint256,uint256,uint256)'.codeUnits),
);
try {
final signedTransaction = await w3mService.signTransaction(transaction);
final response = await web3client.sendRawTransaction(signedTransaction);
print("Transaction sent: $response");
} catch (e) {
print("Error booking space: $e");
}
}
}
This class sets up the Web3 client, manages the MetaMask connection, and provides methods for connecting, disconnecting, and interacting with the blockchain.
Step 3: Using SpaceServices in Your Flutter App
Now that we have our SpaceServices
class, let's see how we can use it in a Flutter widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final spaceServices = Provider.of<SpaceServices>(context);
return Scaffold(
appBar: AppBar(title: Text('RoomFlow')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (spaceServices.isConnected)
Text('Connected: ${spaceServices._userAddress}')
else
Text('Not connected'),
SizedBox(height: 20),
ElevatedButton(
onPressed: spaceServices.isConnected
? spaceServices.disconnectWallet
: spaceServices.connectToMetaMask,
child: Text(spaceServices.isConnected ? 'Disconnect' : 'Connect to MetaMask'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: spaceServices.isConnected
? () => spaceServices.bookSpace(
BigInt.from(1),
BigInt.from(DateTime.now().millisecondsSinceEpoch),
BigInt.from(DateTime.now().add(Duration(days: 1)).millisecondsSinceEpoch),
BigInt.from(1000000000000000000) // 1 ETH in Wei
)
: null,
child: Text('Book Space'),
),
],
),
),
);
}
}
This widget displays the connection status, provides buttons to connect/disconnect the wallet, and shows a "Book Space" button that's only enabled when connected to MetaMask.
Step 4: Setting Up Your Main App
Finally, let's set up the main app to use SpaceServices
:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => SpaceServices(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RoomFlow',
home: HomePage(),
);
}
}
Conclusion
In this tutorial, we've walked through the process of integrating MetaMask into a Flutter dApp. We created a SpaceServices
class that manages the Web3 client and MetaMask connection, and we've seen how to use this service in a Flutter widget to connect a wallet and interact with a smart contract.
Remember, this is a simplified example. In a real-world application, you'd need to handle more edge cases, implement proper error handling, and likely interact with multiple smart contract functions. You'd also want to add more features like displaying transaction history, showing token balances, and handling different networks.
By integrating MetaMask, you're providing your users with a secure and familiar way to interact with your dApp. This can greatly enhance the user experience and make your application more accessible to the growing Web3 community.
Happy coding, and welcome to the world of decentralized applications!