LearnWeb3 Flutter💙

LearnWeb3 Flutter💙

FreshMan Track Dapp challenge

Hey there Flutterians. Thinking of breaking into web3?? So am I. And thats why i decided to learn solidity and what better place to start other than LearnWeb3. I have been completing the freshman track levels blissfully, not until I reached level 7 where I had to connect a Smart Contract to a simple Webpage. Thats right, simple! But the flutter in me couldn`t fall for that and thats why we are here. Trying to share my findings, the process and the result of the detour that became the tour😁.

Well, lets get to business, the Flutter part. To kick off please check this level dapp on the freshman course that covers the Solidity programming part.

1. Setting up an Api service

We need to setup an api that will interact with our smart contract on the Ethereum network. For this I used Infura, a powerful blockchain development suite. Create an account here infura.io After that get to the dashboard and create a new project for the Ethereum network. After that head to the project settings where we need to change the endpoints to Ropsten Test Network.

infurs.png

2.Installing Flutter dependencies

For this simple app we need only some few dependencies:

  • Web3 dart This one connects to the ethereum network to perform operations such as connecting to nodes, interacting with smart contracts etc.

  • http This helps us to connect to the internet and make consume the Apis.


dependencies:
  flutter:
    sdk: flutter

  web3dart: ^2.3.3
  http:
  cupertino_icons: ^1.0.2

3.Setup the Contract ABI

Get the contract ABI from Remix IDE on the compile tab then copy the contents.

abi.png

Create an assets folder on the root folder of the flutter project, then create a new json file

ass.png Also dont forget to import the assets folder in the pubsec.yaml

  assets:
    - assets/

4. Lets Code

We are going to use the already generated main.dart class. Clear the contents of _MyHomePageState class and then add this lines to initialize the variables we need.

String? _mood;
  final TextEditingController _controller = TextEditingController();

  //Used to make network calls by the Web3 dart library
   Client httpClient=Client();

   //Web3 class that sends the request to the Ethereum network
  late Web3Client web3Client;
  final metamaskAddress = 'Input your metamask Rospen account address';

Initialize the web3 client library on the initState function provided by flutter. Remember the Infura endpoints we setup earlier,Pick any and pass it to the Web3 client class. Make sure you are using the Ropsten network as thats the Ethereum testnet we using

@override
  void initState() {
    web3Client = Web3Client('Infura Ropsten Api endpint',
        httpClient);
    geMood(metamaskAddress);
    super.initState();
  }

Next up we load the contract ABI. Please check out the comments in the code for detailed explanation

  Future<DeployedContract> loadContract() async {
    //loads the ABI json file from assets and converts it into a String
    String abi = await rootBundle.loadString('assets/abi.json');
    //Get the contract address from the Deploy tab on Remix IDE
    String contractAddress = 'Input the contract addres from Remix';
    //Pass in the json String and the name of the smart contract class.
    //in this scenario the name of our contract class is MoodDiary
    //Also pass in the Ethereum address which is generated by the utility class from web3 dart libray which 
    //needs you to pass in the contract address
    final contract = DeployedContract(ContractAbi.fromJson(abi, 'MoodDiary'),
        EthereumAddress.fromHex(contractAddress));
    return contract;
  }

Next up we connect our app to the Ethereum network and call any function we would like to as defined in the Smart Contract.

Future<List<dynamic>> query(String functionName, List<dynamic> args) async {
    //loads the contract
    final contract = await loadContract();
    //Find the function we want from the contract abi
    final ethFunction = contract.function(functionName);
    //Call the contract on the Ethereum Rospen test network and return its result
    final result = await web3Client.call(
        contract: contract, function: ethFunction, params: args);
    return result;
  }

The function below is used to create and send a transaction to the Ethereum network and returns a hash of the mined transaction

  Future<String> setMood(String functionName, List<dynamic> args) async {
    EthPrivateKey credential = EthPrivateKey.fromHex(
        'Your Metamask Rospen Account Private Key');//Dont share this with anyone
    final contract = await loadContract();
    final ethFunction = contract.function(functionName);
    //Sends the transaction to the Ethereum network and returns a hash of the mined transaction
    final result = await web3Client.sendTransaction(
        credential,
            //create a transaction using the web3 utility class which is sent on to the Ethereum netwok to be mined
        Transaction.callContract(
          contract: contract,
          function: ethFunction,
          parameters: args,
        ),
        //The chain Id represents the Ethereum network we are operating on.
        //In this case Rospen net testnet whose id is 3,others include rinkeby whose id is 4 etc
        chainId: 3);
    return result;
  }

//Finally we define a function to call the query method above and display the results on our app //The minting may take some time to execute on the network thus give it some time before checking //the mood again

Future<void> geMood(var address) async {
//Pass in the name of the function we want to execute on the smart contract
//The query returns a list of values even though we expect a single String from this 
//contract function.
    List<dynamic> res = await query('getMood', []);
    print(res);
    res.forEach((element) {
      _mood = element;
    });
    setState(() {});
  }

At last we setup a simple UI to interact with the smart contract. A Textfield to get the input and a Text widget to display the mood. Here is the full code which you can tinker with.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String? _mood;
  final TextEditingController _controller = TextEditingController();

  //Used to make network calls by the Web3 dart library
   Client httpClient=Client();

   //Web3 class that sends the request to the Ethereum network
  late Web3Client web3Client;
  final metamaskAddress = 'Input your metamask Rospen account address';

  @override
  void initState() {
    web3Client = Web3Client('Infura Ropsten Api endpint',
        httpClient);
    geMood(metamaskAddress);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Spacer(flex: 1,),
          Text(_mood ?? 'No Mood Yet'),
          const Spacer(flex: 1,),
          Align(
            alignment: Alignment.bottomCenter,
            child: Row(
              children: [
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextField(
                      controller: _controller,
                      decoration: InputDecoration(
                        fillColor: Colors.grey,
                          border: OutlineInputBorder(
                            borderSide: BorderSide(color: Theme.of(context).primaryColor,width: 2),
                              borderRadius:
                                  BorderRadius.all(Radius.circular(5)))),
                    ),
                  ),
                ),
                IconButton(
                    onPressed: () async {
                      var res =await setMood('setMood', [_controller.text]);
                      print(res);
                      geMood(metamaskAddress);
                    },
                    icon: const Icon(Icons.send))
              ],
            ),
          )
        ],
      ),
    );
  }

  Future<DeployedContract> loadContract() async {
    //loads the ABI json file from assets and converts it into a String
    String abi = await rootBundle.loadString('assets/abi.json');
    //Get the contract address from the Deploy tab on Remix IDE
    String contractAddress = 'Input the contract addres from Remix';
    //Pass in the json String and the name of the smart contract class.
    //in this scenario the name of our contract class is MoodDiary
    //Also pass in the Ethereum address which is generated by the utility class from web3 dart libray which 
    //needs you to pass in the contract address
    final contract = DeployedContract(ContractAbi.fromJson(abi, 'MoodDiary'),
        EthereumAddress.fromHex(contractAddress));
    return contract;
  }

  Future<List<dynamic>> query(String functionName, List<dynamic> args) async {
    //loads the contract
    final contract = await loadContract();
    //Find the function we want from the contract abi
    final ethFunction = contract.function(functionName);
    //Call the contract on the Ethereum Rospen test network and return its result
    final result = await web3Client.call(
        contract: contract, function: ethFunction, params: args);
    return result;
  }

  Future<String> setMood(String functionName, List<dynamic> args) async {
    EthPrivateKey credential = EthPrivateKey.fromHex(
        'Your Metamask Rospen Account Private Key');//Dont share this with anyone
    final contract = await loadContract();
    final ethFunction = contract.function(functionName);
    //Sends the transaction to the Ethereum network and returns a hash of the mined transaction
    final result = await web3Client.sendTransaction(
        credential,
            //create a transaction using the web3 utility class which is sent on to the Ethereum netwok to be mined
        Transaction.callContract(
          contract: contract,
          function: ethFunction,
          parameters: args,
        ),
        //The chain Id represents the Ethereum network we are operating on.
        //In this case Rospen net testnet whose id is 3,others include rinkeby whose id is 4 etc
        chainId: 3);
    return result;
  }

  Future<void> geMood(var address) async {
    List<dynamic> res = await query('getMood', []);
    print(res);
    res.forEach((element) {
      _mood = element;
    });
    setState(() {});
  }
}

ALAS.. We have made it. Flutter meets Web3. Till next time folks🍻

Here is the link to the full code on Github [github.com/ndungudedan/learnweb3/tree/main/..