Flutter y Blockchain – Población Dapp

Antes de leer este artículo, echa un vistazo a Flutter y Blockchain – Hello World Dapp . Este tutorial lo guiará a través del proceso de creación de su dapp móvil: ¡ Población en Blockchain!

Este tutorial está destinado a aquellos con un conocimiento básico de Ethereum y contratos inteligentes, que tienen algún conocimiento del marco Flutter pero son nuevos en las dapps móviles.

En este tutorial cubriremos:

  1. Configuración del entorno de desarrollo
  2. Creación de un proyecto de trufa
  3. Escribir el contrato inteligente
  4. Compilación y migración del contrato inteligente
  5. Probando el contrato inteligente
  6. Vinculación de contrato con Flutter
  7. Creación de una interfaz de usuario para interactuar con el contrato inteligente
  8. Interactuando con el Dapp completo

Descripción

La población en blockchain es una aplicación descentralizada simple, que le permitirá almacenar la población de un país específico en blockchain, puede aumentar o disminuir la población según la condición actual del país.

Producción:

Configuración del entorno de desarrollo

Truffle es el marco de desarrollo más popular para Ethereum con la misión de hacer su vida mucho más fácil. Pero antes de instalar truffle, asegúrese de instalar node .

Una vez que tenemos el Node instalado, solo necesitamos un comando para instalar Truffle:

npm install -g truffle

También usaremos Ganache , una string de bloques personal para el desarrollo de Ethereum que puede usar para implementar contratos inteligentes, desarrollar aplicaciones y ejecutar pruebas. Puede descargar Ganache navegando a https://truffleframework.com/ganache y haciendo clic en el botón «Descargar».

Creación de un proyecto de trufa

  1. Cree un proyecto básico de Flutter en su IDE favorito
  2. Inicialice Truffle en el directorio del proyecto flutter ejecutando
truffle init

Estructura de directorios

  • contract/ : Contiene archivo de contrato de solidez.
  • migraciones/ : contiene archivos de script de migración (Truffle usa un sistema de migración para manejar la implementación del contrato).
  • test/ : contiene archivos de script de prueba.
  • truffle-config.js : contiene información de configuraciones de implementación de truffle.

Escribir el contrato inteligente

El contrato inteligente en realidad actúa como lógica de back-end y almacenamiento para nuestra Dapp.

  • Cree un nuevo archivo denominado Population.sol en el directorio contracts/ .
  • Agregue el siguiente contenido al archivo:

Solidity

pragma solidity ^0.5.0;
  
contract Population {
      
}
  • La versión mínima de Solidity requerida se indica en la parte superior del contrato: pragma solidity ^0.5.9; .
  • Las declaraciones se terminan con punto y coma.

Configuración de variables

  • Agregue la siguiente variable en la siguiente línea después del contrato Población { 

Solidity

string public countryName ;
uint public currentPopulation ;
  • countryName y currentPopulation contendrán el nombre y la población del país.
  • Se define como modificador público porque la solidez crea automáticamente funciones getter para todas las variables de estado públicas.

Constructor

Solidity

constructor() public{
  countryName = "Unknown" ;
  currentPopulation = 0 ;
}

El constructor en solidez se ejecuta solo una vez, cuando se crea un contrato y se usa para inicializar el estado del contrato. Aquí solo estamos configurando el valor inicial de las variables.

Función

  1. Agregue la siguiente función al contrato inteligente después de la declaración del constructor que configuramos anteriormente.

Solidity

function set(string memory name, uint popCount) public{
  countryName = name ;
  currentPopulation = popCount ;
}
  
function decrement(uint decrementBy) public{
  currentPopulation -= decrementBy ;
}
  
  
function increment(uint incrementBy) public{
  currentPopulation += incrementBy ;
}
  • La función set se utiliza para establecer el nombre del país y la población actual según lo especificado por el usuario.
  • La función decremento disminuirá la población actual con el conteo especificado.
  • La función de incremento incrementará la población actual con el conteo especificado.

Compilar y Migrar 

Compilacion

  1. En una terminal, asegúrese de estar en la raíz del directorio que contiene el proyecto flutter and truffle, ejecute el siguiente comando:
truffle compile

Debería ver un resultado similar al siguiente:

compilación de trufas

Migración

Verá un archivo JavaScript que ya está en el directoriomigrations / : 1_initial_migration.js . Esto maneja la implementación del contrato de Migraciones.sol para observar las migraciones posteriores de contratos inteligentes y garantiza que no migremos dos veces los contratos sin cambios en el futuro.

Vamos a crear nuestro propio script de migración:

  1. Cree un nuevo archivo llamado 2_deploy_contracts.js en el directoriomigrations / .
  2. Agregue el siguiente contenido al archivo 2_deploy_contracts.js :

Javascript

const Population = artifacts.require("Population");
  
module.exports = function (deployer) {
  deployer.deploy(Population);
};
  • Antes de que podamos migrar nuestro contrato a la string de bloques, debemos tener una string de bloques en ejecución. Para este artículo, vamos a usar Ganache , una string de bloques personal para el desarrollo de Ethereum que puede usar para implementar contratos, desarrollar aplicaciones y ejecutar pruebas. Si aún no lo ha hecho, descargue Ganache y haga doble clic en el icono para iniciar la aplicación. Esto generará una string de bloques que se ejecuta localmente en el puerto 7545.

ganache

  • Agregue el siguiente contenido al archivo truffle-config.js :

Javascript

module.exports = { 
  networks: { 
     development: { 
      host: "127.0.0.1",     // Localhost (default: none) 
      port: 7545,            // Standard Ethereum port (default: none) 
      network_id: "*",       // Any network (default: none) 
     }, 
  }, 
    contracts_build_directory: "./src/artifacts/", 
        
  // Configure your compilers 
  compilers: { 
    solc: {     
        
       // See the solidity docs for advice 
       // about optimization and evmVersion 
        optimizer: { 
          enabled: false, 
          runs: 200 
        }, 
        evmVersion: "byzantium"
    } 
  } 
};
  • Migrando el contrato a blockchain, ejecuta:
truffle migrate

la trufa migra

  • Eche un vistazo a Ganache , la primera cuenta originalmente tenía 100 ether, ahora es más baja debido a los costos de transacción de la migración.

Probando el contrato inteligente

En Truffle, podemos escribir pruebas en JavaScript o Solidity. En este artículo, escribiremos nuestras pruebas en Javascript usando las bibliotecas Chai y Mocha.

  1. Cree un nuevo archivo llamadopoblación.js en el directorio test/ .
  2. Agregue el siguiente contenido al archivo population.js :

Javascript

const Population = artifacts.require("Population") ;
  
contract("Population" , () =>{
    let population = null ;
    before(async () => {
        population = await Population.deployed() ;
    });
  
    it("Setting Current Population" , async () => {
        await population.set("India" , 1388901219) ;
        const name = await population.countryName() ;
        const pop = await population.currentPopulation();
        assert(name === "India") ;
        assert(pop.toNumber() === 1388901219) ;
    });
  
    it("Decrement Current Population" , async () => {
        await population.decrement(100) ;
        const pop = await population.currentPopulation() ;
        assert(pop.toNumber() === 1388901119);
    });
  
    it("Increment Current Population" , async () => {
            await population.increment(200) ;
            const pop = await population.currentPopulation() ;
            assert(pop.toNumber() === 1388901319);
        });
});
  • Población : el contrato inteligente que queremos probar. Comenzamos nuestra prueba importando nuestro contrato de Población usando artefactos.require .
  • Probar las funciones de conjunto , disminución e incremento del contrato inteligente de Population.sol al proporcionarle algunos valores definidos.
  • Truffle importa Chai para que podamos usar la función de afirmación. Pasamos el valor real y el valor esperado, para comprobar que el nombre está configurado correctamente o no, assert(name === “India”); .
  • Para la población actual, devuelve el objeto BigNum pero Javascript no se ocupa de él, por lo tanto, convierte el objeto BigNum en el objeto Javascript normal para verificarlo como afirmación (pop. toNumber() === 1388901219) ;

Ejecutando las pruebas

  • Ejecutando la prueba como:
truffle test
  • Si todas las pruebas pasan, verá la salida de la consola similar a esta:

prueba de trufa

Vinculación de contrato con Flutter

  • En el archivo pubspec.yaml importa los siguientes paquetes:
provider: ^4.3.3
web3dart: ^1.2.3
http: ^0.12.2
web_socket_channel: ^1.2.0
country_pickers: ^1.3.0
  • Además, agregue el activo src/artifacts/Population.json al archivo pubspec.yaml que genera truffle-config.js mientras migramos nuestro contrato.
assets:
     - src/artifacts/Population.json
  1. Cree un nuevo archivo llamado contract_linking.dart en el directorio lib/ .
  2. Agregue el siguiente contenido al archivo:

Dart

import 'package:flutter/foundation.dart';
  
class ContractLinking extends ChangeNotifier {
    
}

Variables

  • Agregue la siguiente variable en la siguiente línea después de la clase ContractLinking extends ChangeNotifier {

Dart

final String _rpcUrl = "http://10.0.2.2:7545";
final String _wsUrl = "ws://10.0.2.2:7545/";
final String _privateKey = "Enter Private Key";

La biblioteca web3dart no enviará transacciones firmadas a los mineros. En cambio, se basa en un cliente RPC para hacer eso. Para la URL de WebSocket, simplemente modifique la URL de RPC. Puede obtener la URL de RPC del ganache:

Ganache – RPC url

  • Obtenga la clave privada de ganache:

Ganache – Clave privada

  • Declare las siguientes variables a continuación:

Dart

Web3Client _client;
bool isLoading = true;
 
String _abiCode;
EthereumAddress _contractAddress;
 
Credentials _credentials;
 
DeployedContract _contract;
ContractFunction _countryName;
ContractFunction _currentPopulation;
ContractFunction _set;
ContractFunction _decrement;
ContractFunction _increment;
 
String countryName;
String currentPopulation;
  1. La variable _client se utilizará para establecer una conexión con el Node ethereum rpc con la ayuda de WebSocket.
  2. La variable isLoading se utilizará para comprobar el estado del contrato.
  3. La variable _abiCode se utilizará para leer el contrato abi .
  4. La variable _contractAddress se utilizará para almacenar la dirección del contrato del contrato inteligente implementado.
  5. La variable _credentials almacenará las credenciales del implementador de contratos inteligentes.
  6. La variable _contract se usará para decirle a Web3dart dónde se declara nuestro contrato inteligente.
  7. Las variables _countryName , _currentPopulation , _set , _decrement y _increment se utilizarán para contener las funciones declaradas en nuestro contrato inteligente Population.sol.
  8. countryName y currentPopulation tendrán el nombre y la población exactos del contrato inteligente.

Funciones

  • Después de declarar las variables anteriores, declare las siguientes funciones debajo:

Dart

ContractLinking() {
  initialSetup();
}
 
initialSetup() async {
    
  // establish a connection to the ethereum rpc node. The socketConnector
  // property allows more efficient event streams over websocket instead of
  // http-polls. However, the socketConnector property is experimental.
  _client = Web3Client(_rpcUrl, Client(), socketConnector: () {
    return IOWebSocketChannel.connect(_wsUrl).cast<String>();
  });
  await getAbi();
  await getCredentials();
  await getDeployedContract();
}
 
Future<void> getAbi() async {
    
  // Reading the contract abi
  final abiStringFile =
      await rootBundle.loadString("src/artifacts/Population.json");
  final jsonAbi = jsonDecode(abiStringFile);
  _abiCode = jsonEncode(jsonAbi["abi"]);
  _contractAddress =
      EthereumAddress.fromHex(jsonAbi["networks"]["5777"]["address"]);
}
 
Future<void> getCredentials() async {
  _credentials = await _client.credentialsFromPrivateKey(_privateKey);
}
 
Future<void> getDeployedContract() async {
    
  // Telling Web3dart where our contract is declared.
  _contract = DeployedContract(
      ContractAbi.fromJson(_abiCode, "Population"), _contractAddress);
    
  // Extracting the functions, declared in contract.
  _countryName = _contract.function("countryName");
  _currentPopulation = _contract.function("currentPopulation");
  _set = _contract.function("set");
  _decrement = _contract.function("decrement");
  _increment = _contract.function("increment");
 
  getData();
}
 
getData() async {
    
  // Getting the current name and population declared in the smart contract.
  List name = await _client
      .call(contract: _contract, function: _countryName, params: []);
  List population = await _client
      .call(contract: _contract, function: _currentPopulation, params: []);
  countryName = name[0];
  currentPopulation = population[0].toString();
  print("$countryName , $currentPopulation");
  isLoading = false;
  notifyListeners();
}
 
addData(String nameData, BigInt countData) async {
    
  // Setting the countryName  and currentPopulation defined by the user
  isLoading = true;
  notifyListeners();
  await _client.sendTransaction(
      _credentials,
      Transaction.callContract(
          contract: _contract,
          function: _set,
          parameters: [nameData, countData]));
  getData();
}
 
increasePopulation(int incrementBy) async {
    
  // Increasing the currentPopulation
  isLoading = true;
  notifyListeners();
  await _client.sendTransaction(
      _credentials,
      Transaction.callContract(
          contract: _contract,
          function: _increment,
          parameters: [BigInt.from(incrementBy)]));
  getData();
}
 
decreasePopulation(int decrementBy) async {
    
  // Decreasing the currentPopulation
  isLoading = true;
  notifyListeners();
  await _client.sendTransaction(
      _credentials,
      Transaction.callContract(
          contract: _contract,
          function: _decrement,
          parameters: [BigInt.from(decrementBy)]));
  getData();
}

Creación de una interfaz de usuario para interactuar con el contrato inteligente

  1. Cree un nuevo archivo llamado display_population.dart en el directorio lib/ .
  2. Agregue el siguiente contenido al archivo:

Dart

import 'package:country_pickers/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:population/set_population.dart';
import 'package:provider/provider.dart';
import 'package:population/contract_linking.dart';
  
class DisplayPopulation extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final contractLink = Provider.of<ContractLinking>(context);
  
    return Scaffold(
      appBar: AppBar(
        title: Text("Population On Blockchain"),
        centerTitle: true,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.edit),
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                  builder: (context) => SetPopulation(),
                  fullscreenDialog: true));
        },
      ),
      body: Container(
        padding: EdgeInsets.symmetric(horizontal: 20),
        child: Center(
          child: contractLink.isLoading
              ? CircularProgressIndicator()
              : SingleChildScrollView(
                  child: Column(
                    children: [
                      contractLink.countryName == "Unknown"
                          ? Icon(
                              Icons.error,
                              size: 100,
                            )
                          : Container(
                              child: CountryPickerUtils.getDefaultFlagImage(
                                  CountryPickerUtils.getCountryByIsoCode(
                                      contractLink.countryName)),
                              width: 250,
                              height: 150,
                            ),
                      Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Text(
                          "Country - ${contractLink.countryName}",
                          style: TextStyle(
                              fontSize: 30,
                              fontWeight: FontWeight.bold,
                              color: Theme.of(context).accentColor),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Text(
                            "Population - ${contractLink.currentPopulation}",
                            style: TextStyle(
                                fontSize: 25,
                                fontWeight: FontWeight.bold,
                                color: Theme.of(context).primaryColor)),
                      ),
                      contractLink.countryName == "Unknown"
                          ? Text("")
                          : Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  ElevatedButton.icon(
                                    onPressed: () {
                                      dialog(context, "Increase");
                                    },
                                    icon:
                                        Icon(Icons.person_add_alt_1, size: 18),
                                    label: Text("Increase"),
                                  ),
                                  SizedBox(
                                    width: 15,
                                  ),
                                  ElevatedButton.icon(
                                    onPressed: () {
                                      if (contractLink.currentPopulation !=
                                          "0") {
                                        dialog(context, "Decrease");
                                      }
                                    },
                                    icon: Icon(Icons.person_remove_alt_1,
                                        size: 18),
                                    label: Text("Decrease"),
                                  )
                                ],
                              ),
                            )
                    ],
                  ),
                ),
        ),
      ),
    );
  }
  
  dialog(context, method) {
    final contractLink = Provider.of<ContractLinking>(context, listen: false);
    TextEditingController countController = TextEditingController();
    showDialog(
        context: context,
        builder: (_) => AlertDialog(
              title: method == "Increase"
                  ? Text("Increase Population")
                  : Text("Decrease Population"),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                      "Current Population is ${contractLink.currentPopulation}"),
                  Padding(
                    padding: EdgeInsets.only(top: 20.0),
                    child: TextField(
                      controller: countController,
                      keyboardType: TextInputType.number,
                      decoration: InputDecoration(
                        border: OutlineInputBorder(),
                        hintText: method == "Increase"
                            ? "Increase Population By ..."
                            : "Decrease Population By ...",
                      ),
                    ),
                  )
                ],
              ),
              actions: <Widget>[
                Row(
                  children: [
                    TextButton(
                      child: Text("Cancel"),
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                    ),
                    TextButton(
                      child: method == "Increase"
                          ? Text("Increase")
                          : Text("Decrease"),
                      onPressed: () {
                        method == "Increase"
                            ? contractLink.increasePopulation(
                                int.parse(countController.text))
                            : contractLink.decreasePopulation(
                                int.parse(countController.text));
                        Navigator.of(context).pop();
                      },
                    ),
                  ],
                )
              ],
            ));
  }
}
  1. Cree otro archivo nuevo llamado set_population.dart en el directorio lib/ .
  2. Agregue el siguiente contenido al archivo:

Dart

import 'package:country_pickers/country.dart';
import 'package:country_pickers/country_pickers.dart';
import 'package:flutter/material.dart';
import 'package:population/contract_linking.dart';
import 'package:provider/provider.dart';
  
class SetPopulation extends StatefulWidget {
  @override
  _SetPopulationState createState() => _SetPopulationState();
}
  
class _SetPopulationState extends State<SetPopulation> {
  Country _selectedCountry = CountryPickerUtils.getCountryByIsoCode('AF');
  
  TextEditingController countryNameController =
      TextEditingController(text: "Unknown");
  TextEditingController populationController = TextEditingController();
  
  @override
  Widget build(BuildContext context) {
    final contractLink = Provider.of<ContractLinking>(context);
  
    return Scaffold(
        appBar: AppBar(
          title: Text("Set Population"),
          actions: [
            TextButton(
                onPressed: () {
                  contractLink.addData(countryNameController.text,
                      BigInt.from(int.parse(populationController.text)));
                  Navigator.pop(context);
                },
                child: Text(
                  "SAVE",
                  style: TextStyle(
                      color: Colors.brown, fontWeight: FontWeight.bold),
                ))
          ],
        ),
        body: Container(
          child: Center(
            child: SingleChildScrollView(
              child: Column(
                children: [
                  stackCard(countryFlagPicker(), countryFlag()),
                  stackCard(populationTextfield(), countryFlag()),
                ],
              ),
            ),
          ),
        ));
  }
  
  Widget stackCard(Widget widget1, Widget widget2) {
    return Stack(
      children: [
        Padding(
          padding: EdgeInsets.only(bottom: 8.0, left: 54),
          child: Container(
            height: 120,
            child: Card(
              color: Colors.cyan,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [widget1],
              ),
            ),
          ),
        ),
        widget2
      ],
    );
  }
  
  Widget countryFlag() {
    return Positioned(
      top: 15,
      child: Container(
        width: 100,
        height: 100,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
        ),
        child: CircleAvatar(
            backgroundColor: Colors.blueGrey,
            child: Container(
                width: 80,
                height: 50,
                child:
                    CountryPickerUtils.getDefaultFlagImage(_selectedCountry))),
      ),
    );
  }
  
  Widget countryFlagPicker() {
    return CountryPickerDropdown(
      underline: Container(
        height: 2,
        color: Colors.black,
      ),
      onValuePicked: (Country country) {
        print("${country.name}");
        setState(() {
          _selectedCountry = country;
          countryNameController.text = country.isoCode;
        });
      },
      itemBuilder: (Country country) {
        return Row(
          children: <Widget>[
            SizedBox(width: 48.0),
            CountryPickerUtils.getDefaultFlagImage(country),
            SizedBox(width: 8.0),
            Expanded(
                child: Text(
              country.name,
              style: TextStyle(
                  color: Colors.brown,
                  fontSize: 25,
                  fontWeight: FontWeight.bold),
            )),
          ],
        );
      },
      icon: Icon(
        Icons.arrow_downward,
        color: Colors.white,
        size: 50,
      ),
      itemHeight: null,
      isExpanded: true,
    );
  }
  
  Widget populationTextfield() {
    return Padding(
      padding: EdgeInsets.only(left: 48.0, right: 5),
      child: TextField(
        decoration: InputDecoration(
            filled: true,
            fillColor: Colors.black,
            focusedBorder: OutlineInputBorder(),
            labelText: "Population",
            labelStyle: TextStyle(color: Colors.white),
            hintText: "Enter Population",
            prefixIcon: Icon(Icons.person_pin_outlined)),
        keyboardType: TextInputType.number,
        controller: populationController,
      ),
    );
  }
}
  • Actualice main.dart como:

Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:population/display_population.dart';
import 'package:provider/provider.dart';
import 'package:population/contract_linking.dart';
  
void main() => runApp(MyApp());
  
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ContractLinking>(
      create: (_) => ContractLinking(),
      child: MaterialApp(
        title: 'Population On Blockchain',
        theme: ThemeData(
            brightness: Brightness.dark,
            primaryColor: Colors.cyan[400],
            accentColor: Colors.deepOrange[200]),
        home: DisplayPopulation(),
      ),
    );
  }
}

Interactuando con el Dapp completo

  1. ¡Ahora estamos listos para usar nuestro dapp!
  2. Simplemente EJECUTE el Proyecto Flutter.

Como puede ver, countryName ( Unknown ) y currentPopulation ( 0 ) en realidad provienen del contrato inteligente, que estableceríamos en el constructor de nuestro contrato inteligente.

Cuando hace clic en el botón de acción flotante , empujará la ruta actual a SetPopulation().

  1. Puede definir el nombre del país y la población actual aquí.
  2. Cuando hace clic en guardar, el nombre y la población especificados se agregarán a la string de bloques.

  • Puede aumentar o disminuir la población actual simplemente haciendo clic en el botón elevado como se especifica.

¡Felicidades! Ha dado un gran paso para convertirse en un desarrollador de dapp móvil de pleno derecho. Para desarrollar localmente, tiene todas las herramientas que necesita para comenzar a crear dapps más avanzados.

Si se quedó atascado en algún lugar, consulte el repositorio de GitHub para obtener el código completo.

Publicación traducida automáticamente

Artículo escrito por aniketambore y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *