Implementing a Decentralized Voting App with Flutter and Celo

Introduction

In our world today, building trust in electronic voting systems is a significant challenge. To solve this, we’ll explore how to create a decentralized voting application using Flutter and Celo. With the power of blockchain technology, we’ll develop a secure and transparent voting system that’s resistant to tampering and fraud. By the end of this tutorial, not only will you have a fully functioning voting app, but you’ll also gain an understanding of how blockchain technology can securely handle sensitive data, like votes.

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:

  1. Flutter: We will be using Flutter for our mobile application development. Ensure that it’s installed and properly set up on your system.
  2. Dart: Since Flutter apps are written in Dart, you must have Dart SDK installed.
  3. A Celo Wallet: Make sure to set up a wallet on Celo’s Alfajores Testnet. You can do this by following the steps from the Setting up a Celo Wallet section in this tutorial.

Creating the Smart Contract

Smart contracts play a crucial role in decentralized applications. They define the rules of interaction and ensure the immutability of data on the blockchain. For our voting app, we’ll implement a smart contract for vote counting and results storage.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Voting {
    // candidate struct
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    mapping(uint => Candidate) public candidates;
    mapping(address => bool) public voters;
    uint public candidatesCount;

    event votedEvent (
        uint indexed _candidateId
    );

    constructor () {
        addCandidate("Candidate 1");
        addCandidate("Candidate 2");
        addCandidate("Candidate 3");
        addCandidate("Candidate 4");
    }

    function addCandidate (string memory _name) private {
        candidatesCount ++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

    function vote (uint _candidateId) public {
        require(!voters[msg.sender]);
        require(_candidateId > 0 && _candidateId <= candidatesCount);

        voters[msg.sender] = true;
        candidates[_candidateId].voteCount ++;

        emit votedEvent(_candidateId);
    }
}

This contract creates four candidates and provides a function to vote for them. When a vote is cast, it checks to make sure each account can vote only once and that votes are only cast for valid candidates. Then, it increases the candidate’s vote count and triggers a votedEvent.

We are not covering the deployment of this contract in this tutorial. You can deploy this contract to the Celo Alfajores testnet using Remix, Truffle, or Hardhat, and obtain the contract's address and ABI.

Setting Up the Flutter Application

To begin, we need to create a new Flutter application. Open up your terminal and run the following command:

$ flutter create voting_app
$ cd voting_app

Interacting with the Smart Contract

To interact with our smart contract, we’re going to use a Dart package called web3dart. This package allows us to connect to the Ethereum blockchain (and in our case, the Celo blockchain since it’s Ethereum compatible) and interact with smart contracts.

First, we need to add web3dart, http, and web_socket_channel packages to our pubspec.yaml file by running the following command:

flutter pub add web3dart
flutter pub add http
flutter pub add web_socket_channel

We will also need our contract’s ABI (Application Binary Interface), which is a JSON file that describes how to interact with our contract. This is generated when you compile your Solidity contract. Place it in the assets folder (assets/ABI.json ) and reference it in the pubspec.yaml file:

flutter:
  uses-material-design: true
  assets:
    - assets/

Now, let’s create a new Dart file (lib/contract_link.dart ) where we’ll setup a connection to our smart contract:

import 'dart:convert';

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

import 'candidate.dart';

class ContractLink {
  String rpcUrl = 'https://alfajores-forno.celo-testnet.org';
  String wsUrl = 'wss://alfajores-forno.celo-testnet.org/ws';

  late Web3Client _client;
  late DeployedContract _contract;
  late Credentials _credentials;

  ContractLink() {
    _client = Web3Client(rpcUrl, Client(), socketConnector: () {
      return IOWebSocketChannel.connect(wsUrl).cast<String>();
    });

    _getContract();
  }

  _getContract() async {
    String abiString = await rootBundle.loadString('assets/ABI.json');
    var abi = jsonDecode(abiString);

    String contractAddress =
        "CONTRACT_ADDRESS"; // replace with your contract address

    _contract = DeployedContract(
      ContractAbi.fromJson(jsonEncode(abi), 'Voting'),
      EthereumAddress.fromHex(contractAddress),
    );

    _credentials = EthPrivateKey.fromHex(
        '`private_key`');
  }
  // More code will be added here for contract interaction...

}

This ContractLink class creates a connection to the Celo Alfajores testnet and sets up interaction with our smart contract. Make sure to replace CONTRACT_ADDRESS with your deployed contract address and replace private_key with your deployed contract address.

Extending the ContractLink class

Go back to the contract_link.dart file and append the following methods:

  Future<List<dynamic>> query(String functionName, List<dynamic> args) async {
    final function = _contract.function(functionName);
    return _client.call(contract: _contract, function: function, params: args);
  }

  Future<void> submit(String functionName, List<dynamic> args) async {
    final function = _contract.function(functionName);
    await _client.sendTransaction(
      _credentials,
      Transaction.callContract(
        contract: _contract,
        function: function,
        parameters: args,
      ),
      chainId: 44787,
    );
  }

  Future<List<Candidate>> getCandidates() async {
    List<Candidate> candidatesList = [];
    var candidatesCount = await query('candidatesCount', []);

    for (var i = 1; i <= int.parse(candidatesCount[0].toString()); i++) {
      var temp = await query('candidates', [BigInt.from(i)]);
      candidatesList.add(Candidate.fromArray(temp));
    }

    return candidatesList;
  }

  Future<void> vote(int candidateId) async {
    await submit('vote', [BigInt.from(candidateId)]);
  }

In the above code, query and submit are general purpose functions to read from and write to the blockchain. getCandidates uses the query function to fetch all the candidates from the smart contract. vote function uses the submit function to cast a vote for a candidate.

Next, we need a Candidate class to represent our candidates. Create a new Dart file (lib/candidate.dart) and add the following code:

class Candidate {
    final int id;
    final String name;
    final int voteCount;

    Candidate(this.id, this.name, this.voteCount);

    Candidate.fromArray(List<dynamic> array) :
        id = array[0].toInt(),
        name = array[1],
        voteCount = array[2].toInt();
}

Building the User Interface

Next, let’s build a simple UI for our app. Open the lib/main.dart file and replace the existing code with the following:

import 'package:flutter/material.dart';
import 'package:voting_app/candidate.dart';
import 'package:voting_app/contract_link.dart';

void main() {
  runApp(
    const MaterialApp(
      title: 'Celo Voting',
      home: HomePage(),
    ),
  );
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late ContractLink _contractLink;

  @override
  void initState() {
    _contractLink = ContractLink();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Celo Voting'),
      ),
      body: FutureBuilder<List<Candidate>>(
        future: _contractLink.getCandidates(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }
          return ListView.builder(
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              var candidate = snapshot.data![index];

              return Card(
                child: ListTile(
                  title: Text(candidate.name),
                  trailing: Text('Votes: ${candidate.voteCount}'),
                  onTap: () {
                    _contractLink.vote(candidate.id);
                    setState(() {});
                  },
                ),
              );
            },
          );
        },
      ),
    );
  }
}

This code defines a simple Flutter app with a single screen that fetches the list of candidates from the blockchain and displays them in a list. When a list item is tapped, a vote is cast for the associated candidate.

That’s it! Now you have a decentralized voting app built with Flutter and Celo. Run the app using the flutter run command and cast your votes!

You should have an interface of this form when you run the app:

Conclusion

Congratulations! You’ve just built a decentralized voting app using Flutter and Celo. Through this tutorial, you’ve learned how to:

  • Write a smart contract for a voting system.
  • Deploy the contract to the Celo network.
  • Interact with the contract using Flutter and web3dart.

You’ve also gained a deeper understanding of how blockchain can be used to create a transparent and secure voting system.

Next Steps

As an exercise, you can enhance this voting app by adding features like:

  • Adding user authentication and ensuring that a user can only vote once.
  • Implementing a feature to add new candidates to the voting system.
  • Enhancing the UI to make it more user-friendly and visually appealing.

Remember, the potential of blockchain technology is vast, and there’s so much more you can learn and do. To further explore Celo and dApp development, you can check out Celo’s official documentation.

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

I hope you found this tutorial helpful and wish you best of luck in your future blockchain development endeavours!

6 Likes

Welcome @Balq ,

The system shows this is your first-time post. Welcome to the platform again.

1 Like

Yeah true

Thanks @bobelr

1 Like

You’re welcome @Balq

1 Like

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

1 Like

Welcome on board, Sage.

I need a reviewer @Phenzic @ishan.pathak2711

I will be reviewing this

1 Like

okay thanks @thompsonogoyi1t