Implementing Smart Contracts On Flutter DApps
Learn how to build a note-taking app on Flutter by integrating smart contracts through this tutorial

Decentralized Application
A decentralized application(DApp) is an application that is meant to run in a decentralized computing system/blockchain and it was initially introduced by Ethereum Blockchain which is built using smart contracts. When using the Ethereum ecosystem, we have to use Solidity, a language which was built for writing smart contracts. Solidity is an OOP, high-level language for writing and implementing smart contracts that can later govern accounts and transactions on the Ethereum blockchain. In this article, we will build a web3 DApp project using Solidity and Flutter. We will use the web3dart package to interact with the Ethereum blockchain in our Flutter application.
Note: When following this tutorial, I am assuming that the reader has basic knowledge of Ethereum, smart contracts and the Flutter framework.
Things we will be covering in this article:
- Setting up development for Solidity and Flutter app.
- Setting up Truffle project
- Creating smart contracts in solidity
- Compiling and migrating smart contract
- Linking smart contracts in the Flutter app
- Creating awesome UI for the Flutter app
We will be creating a note-taking app in which all the notes will be stored in a blockchain using smart contracts.
Setting up development for Solidity
We will be using Truffle for developing smart contracts locally. Truffle is a popular framework for building and testing smart contracts using the Ethereum Virtual Machine(EVM). Truffle can be easily installed using the following command:
npm install -g truffle
We will use Ganache, which provides a testing Ethereum blockchain so that we can test and deploy smart contracts before publishing it.
Setting up the Truffle project
- To initialize Truffle, we need to create a Flutter project first using the following command:
flutter create notetaking_dapp
cd notetaking_dapp
- Once we have the basic Flutter app ready, we can initialize truffle by creating a folder named
contractsand running:
mkdir contracts
cd contracts
truffle init

This command will create the following folders for you:
contracts/: Contains smart contract code.migrations/: Contains migrations scripts which will be used be truffle to handle deployment.test/: Contains test scriptstruffle-config.js: Contains truffle configurations
Creating smart contracts in Solidity
- Create
NotesContract.solfile incontracts/directory and add the following code:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.5.16;
contract NotesContract {
uint256 public notesCount;
struct Notes {
uint256 id;
string noteTitle;
string noteContent;
uint256 timeStamp;
}
mapping(uint256 => Notes) public notes;
constructor() public {
notesCount = 0;
}
event NoteAdded(uint256 _id);
event NoteDeleted(uint256 _id);
event NoteEdited(uint256 _id);
}
notesCountis an unsigned public integer that stores the total number of notes in the smart contract.struct Notesis the data structure for a note. It defines if and whether a note should contain an id, a title, content and timestamp. In this scenario, timestamp is an unsigned integer as we are already storing the time stamp in the Unix epoch. Unix epoch is the number of seconds that have elapsed since January 1, 1970.notesis the map that stores all the notes with anidas key andNotes structas value.- Create a constructor and set
notesCount = 0.NoteAdded,NoteDeleted, andNoteEditedare the events that will be emitted by blockchain so that all the DApps can listen to these emitted events and function accordingly. All these events will emit IDs of specific notes along with them. Execute the following code to witness this:
function addNote(
string memory _noteTitle,
string memory _noteContent,
uint256 timeStamp
) public {
notes[notesCount] = Notes(
notesCount,
_noteTitle,
_noteContent,
timeStamp
);
emit NoteAdded(notesCount);
notesCount++;
}
addNotefunction accepts note title, note content, and the time stamp as parameters. We have to create a new struct of Notes with these data and assign it to the currentnotesCountvalue in thenotesmap. Once we have added theNotesstruct to the map, we have to emit theNotesAddedevent with the currentnotesCount. After emitting the event we can increment thenotesCountvariable for the next note. Add the following function after the events to go to the next step:
function deleteNote(uint256 _id) public {
delete notes[_id];
emit NoteDeleted(_id);
}
- Create a
deleteNotefunction which accepts the ID of the note to be deleted. First, we have to remove the Notes from thenotesmap corresponding to the received ID. After that, we can emit theNoteDeletedevent using the following code:
function editNote(
uint256 _id,
string memory _noteTitle,
string memory _noteContent
) public {
notes[_id] = Notes(_id, _noteTitle, _noteContent, notes[_id].timeStamp);
emit NoteAdded(_id);
}
- For updating notes, create the
editNotefunction that accepts the ID of the note to be edited, title, and content. Now create a new struct with these values and assign it to the received ID in thenotesmap.
We are done with executing the smart contract code. Now we can compile and migrate smart contracts!
Compiling and migrating smart contract
In the terminal, go to the contracts directory and run the following command:
truffle compile
You should see an output similar to the image given below:

Migration
- In the
migrations/directory, you will notice a file called1_initial_migration.jswhich handles the deployment of theMigrations.solcontract. Each contract needs one migration file. Create a new file named2_notes_contract_migration.jsin themigrations/directory and add the following code in that file:
const NotesContract = artifacts.require("NotesContract");
module.exports = function (deployer) {
deployer.deploy(NotesContract);
};
- Before running the migration, we need a local blockchain up and running on our system. For that, start the Ganache app which will start an instance of Blockchain on port 7545 as displayed below:

- Now open the
truffle-config.jsand add the following content:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*",
},
advanced: {
websocket: true,
},
},
contract_build_directory: "./src/abis",
compilers: {
solc: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
};
- Run the following code from the
contract/directory:
truffle migrate
- Once the above code is successfully executed, the first account will have slightly less than 100ETH as you can witness in Ganche which is due to the transaction cost of migrating smart contracts on the blockchain.
Linking smart contracts
- In your
pubspec.yamlfile add the following packages:
# Needed for interaction with smart contract
http: ^0.13.3
web3dart: ^2.1.4
web_socket_channel: ^2.1.0
provider: ^5.0.0
# Needed for UI
google_fonts: ^2.1.0
flutter_staggered_grid_view: ^0.4.0
- Add
contracts/build/contracts/NotesContract.jsonfile as an asset in yourpubspec.yamlwhich can also generated by thetruffle migratecommand. Use the following code:
assets:
- contracts/build/contracts/NotesContract.json
- Now create a
note_controller.dartfile in thelibdirectory and add the following code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:notetaking_dapp/models/note.dart';
import 'package:web3dart/web3dart.dart';
import 'package:web_socket_channel/io.dart';
class NoteController extends ChangeNotifier {
List<Note> notes = [];
bool isLoading = true;
int noteCount;
final String _rpcUrl = "http://127.0.0.1:7545";
final String _wsUrl = "ws://127.0.0.1:7545/";
final String _privateKey =
"e3beb61794a259037a8ca3379f15d61a82e9bb631ba248ffe14283fdc90c04c8";
Web3Client _client;
String _abiCode;
Credentials _credentials;
EthereumAddress _contractAddress;
DeployedContract _contract;
ContractFunction _notesCount;
ContractFunction _notes;
ContractFunction _addNote;
ContractFunction _deleteNote;
ContractFunction _editNote;
ContractEvent _noteAddedEvent;
ContractEvent _noteDeletedEvent;
}
Here are some important considerations to be looked into:
- Importing all the required packages required.
- Creating a
ChangeNotifierclass calledNoteController. - Get the RPC URL from Ganache -

- Get the Private key of any account from Ganache by clicking on the Key icon.

_clientvariable can used to connect to Ethereum node withWebSockets._abiCodeis used to store the ABI of the smart contract. Read this to know more about ABI._credentialswill contain credentials of the smart contract deployer._contractAddresswill contain the smart contract's address on the blockchain._contractis the instance of our smart contract which will be used to call all the functions._notesCount,_notes,_addNote,_deleteNote,_editNoteare the functions that are deployed on smart contracts._noteAddedEventand_noteDeletedEventare the events emitted by our contract which can be listed in the flutter app.After variable declaration, create the following functions in
note_controller.dart:
NoteController() {
init();
}
init() async {
_client = Web3Client(_rpcUrl, Client(), socketConnector: () {
return IOWebSocketChannel.connect(_wsUrl).cast<String>();
});
await getAbi();
await getCreadentials();
await getDeployedContract();
}
Future<void> getAbi() async {
String abiStringFile = await rootBundle
.loadString("contracts/build/contracts/NotesContract.json");
var jsonAbi = jsonDecode(abiStringFile);
_abiCode = jsonEncode(jsonAbi['abi']);
_contractAddress =
EthereumAddress.fromHex(jsonAbi["networks"]["5777"]["address"]);
}
Future<void> getCreadentials() async {
_credentials = await _client.credentialsFromPrivateKey(_privateKey);
}
Future<void> getDeployedContract() async {
_contract = DeployedContract(
ContractAbi.fromJson(_abiCode, "NotesContract"), _contractAddress);
_notesCount = _contract.function("notesCount");
_notes = _contract.function("notes");
_addNote = _contract.function("addNote");
_deleteeNote = _contract.function("deleteNote");
_editNote = _contract.function("editNote");
_noteAddedEvent = _contract.event("NoteAdded");
_noteDeletedEvent = _contract.event("NoteDeleted");
await getNotes();
}
- In
getAbi()function, we are fetching theNotesContract.jsonasset and decoding it to get json data. From that data, we can extract the ABI content and address of the deployed smart contract. - In
getCreadentials()function we will be passing our private key in_client.credentialsFromPrivateKey()function which returns an instance of theCredentialsclass. - In
getDeployedContract(), we will be creating an instance ofDeployedContractusingabiCode, contract name, and contract address. Once we have an instance of the contract we can create instances of all the functions and events we have in our smart contract as shown above.
- After these functions paste the following code to perform the CRUD operation.
getNotes() async {
List notesList = await _client
.call(contract: _contract, function: _notesCount, params: []);
BigInt totalNotes = notesList[0];
noteCount = totalNotes.toInt();
notes.clear();
for (int i = 0; i < noteCount; i++) {
var temp = await _client.call(
contract: _contract, function: _notes, params: [BigInt.from(i)]);
if (temp[1] != "")
notes.add(
Note(
id: temp[0].toString(),
title: temp[1],
body: temp[2],
created:
DateTime.fromMillisecondsSinceEpoch(temp[3].toInt() * 1000),
),
);
}
isLoading = false;
notifyListeners();
}
addNote(Note note) async {
isLoading = true;
notifyListeners();
await _client.sendTransaction(
_credentials,
Transaction.callContract(
contract: _contract,
function: _addNote,
parameters: [
note.title,
note.body,
BigInt.from(note.created.millisecondsSinceEpoch),
],
),
);
await getNotes();
}
deleteNote(int id) async {
isLoading = true;
notifyListeners();
await _client.sendTransaction(
_credentials,
Transaction.callContract(
contract: _contract,
function: _deleteeNote,
parameters: [BigInt.from(id)],
),
);
await getNotes();
}
editNote(Note note) async {
isLoading = true;
notifyListeners();
print(BigInt.from(int.parse(note.id)));
await _client.sendTransaction(
_credentials,
Transaction.callContract(
contract: _contract,
function: _editNote,
parameters: [BigInt.from(int.parse(note.id)), note.title, note.body],
),
);
await getNotes();
}
getNotes()function is used to fetch all the notes present on the blockchain. First, we shall call the_notesCountfunction which will give us the total number of notes present in blockchain and then we can run a for loop, fetch each note and add it into a list of notes in Flutter.addNote()function is used when we want to create a new note. Since we are adding data to the blockchain this is considered as a transaction and hence we shall call the_client.sendTransaction()function and pass it through our_credentialswhich will then be used to pay gas fees for that transaction.deleteNote()function is used when we want to delete a specific note from the blockchain. Similar to theaddNotefunction, we will be making changes in the state of the blockchain by deleting a note. Hence, we have to make a transaction as we did in theaddNotefunction and pass_credentials.editNote()function is used to modify any data in a specific note. This is similar to add and delete note function.- In
addNote,deleteNoteandeditNotewe will be callinggetNote()function towards the end to get the updated notes from blockchain.
Creating a UI for the Flutter app
- Before creating the UI we have to wrap out the
MaterialAppwidget inmain.dartwithChangeNotifierProviderand pass an instance ofNoteControllerso that we can use all the contract functions and values in our app. Refer to the code given below:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => NoteController(),
child: MaterialApp(
title: 'Note Taking DApp',
theme: ThemeData.dark(),
home: HomeScreen(),
),
);
}
}
- Once we are done with setting up the provider for
NoteController, we can start creating UI. You can create ahome_screen.dartfile and paste the following code to do this:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:notetaking_dapp/controllers/note_controller.dart';
import 'package:notetaking_dapp/models/note.dart';
import 'package:notetaking_dapp/screens/notes_details_screen.dart';
import 'package:notetaking_dapp/screens/notes_edit_screen.dart';
import 'package:notetaking_dapp/utils/themes.dart';
import 'package:provider/provider.dart';
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
NoteController noteController;
List<Note> notes;
@override
Widget build(BuildContext context) {
noteController = Provider.of<NoteController>(context, listen: true);
notes = noteController.notes;
return Scaffold(
floatingActionButton: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NotesEditScreen(),
),
);
},
child: Container(
height: 60,
width: 60,
decoration: BoxDecoration(
color: Color(0xFF80DFEA),
borderRadius: BorderRadius.circular(200),
),
child: Center(
child: Text(
"+",
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w500,
),
),
),
),
),
backgroundColor: ColorConstant.bg,
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: noteController.isLoading
? Center(
child: CircularProgressIndicator(),
)
: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Notes",
style: GoogleFonts.montserrat(
fontSize: 40,
color: Colors.grey[100],
),
),
Container(
decoration: BoxDecoration(
color: ColorConstant.bgAccent,
borderRadius: BorderRadius.circular(15),
),
padding: EdgeInsets.all(10),
child: Icon(
Icons.search,
color: Colors.white,
size: 30,
),
),
],
),
SizedBox(height: 30),
Expanded(
child: new StaggeredGridView.countBuilder(
crossAxisCount: 2,
itemCount: notes.length,
itemBuilder: (BuildContext context, int index) {
bool isThirdNote = (index + 1) % 3 == 0;
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NotesDetailsScreen(
note: notes[index],
),
),
);
},
child: Container(
decoration: BoxDecoration(
color: ColorConstant.notesBg[index % 5],
borderRadius: BorderRadius.circular(12),
),
padding: EdgeInsets.all(25),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
notes[index].title,
style: GoogleFonts.montserrat(
fontSize: isThirdNote ? 32 : 24,
fontWeight: FontWeight.w500,
),
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
Text(
"May 21, 2021",
style: GoogleFonts.montserrat(
fontSize: 18,
fontWeight: FontWeight.w500,
color: ColorConstant
.noteTextBg[index % 5]
.withOpacity(1),
),
),
],
),
),
);
},
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(
(index + 1) % 3 == 0 ? 2 : 1, 1),
mainAxisSpacing: 16,
crossAxisSpacing: 16,
),
),
],
),
),
),
),
);
}
}
Sneak Peek
This is the Note-taking app we should have created by following this tutorial.

Here is the source code of this application for your reference.
Conclusion
In this article, we have seen how we can create a smart contract using Solidity language while leveraging the Truffle framework to easily deploy and test out smart contracts locally. We have also learned how to integrate a smart contract using the web3dart package in a flutter. There are endless possibilities on what we can do with smart contracts and Decentralized apps. If you end up creating something cool do let me know @Viral Sangani.
That's all for this article. Thank you so much for reading 😊.






