<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: joelytic s </title>
    <description>The latest articles on DEV Community by joelytic s  (@joel207).</description>
    <link>https://dev.to/joel207</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1734426%2Fcfc0effd-3d23-4a5c-a4d6-5b39695ac9ae.jpeg</url>
      <title>DEV Community: joelytic s </title>
      <link>https://dev.to/joel207</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joel207"/>
    <language>en</language>
    <item>
      <title>Integrating Mpesa API in Flutter Using Clean Architecture</title>
      <dc:creator>joelytic s </dc:creator>
      <pubDate>Fri, 28 Feb 2025 12:45:29 +0000</pubDate>
      <link>https://dev.to/joel207/integrating-mpesa-api-in-flutter-using-clean-architecture-1ad9</link>
      <guid>https://dev.to/joel207/integrating-mpesa-api-in-flutter-using-clean-architecture-1ad9</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Money transfer is a crucial service worldwide. Imagine you're on vacation, stranded on an island, and suddenly realize you've run out of cash - with no credit or debit card in sight. A stressful situation, right? This is where mobile money transfer services step in to save the day.&lt;/p&gt;

&lt;p&gt;People constantly need to send money - whether it's from a customer to a business (C2B), a business to a customer (B2C), or even between businesses (B2B). In Kenya, M-PESA, a mobile money service by Safaricom, has transformed digital payments, making transactions fast and seamless.&lt;/p&gt;

&lt;p&gt;In this guide, we'll learn how to integrate M-PESA STK Push into a Flutter app using the mpesa_flutter_plugin. By following Clean Architecture, we'll ensure the app is scalable, maintainable, and secure for handling payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving into implementation, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Flutter environment set up&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.safaricom.co.ke/" rel="noopener noreferrer"&gt;Safaricom Developer's portal&lt;/a&gt; acc&lt;/li&gt;
&lt;li&gt;Basic knowledge of Flutter and Dart&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding of Clean Architecture principles
&lt;/h2&gt;

&lt;p&gt;Understanding Clean Architecture in M-PESA Flutter Integration&lt;br&gt;
Clean Architecture helps separate concerns, making our M-PESA payment integration scalable, testable, and maintainable.&lt;/p&gt;

&lt;p&gt;Key Layers in Our Flutter App&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Domain Layer – Defines business logic (PaymentEntity, Use Cases, Repository Interface).&lt;/li&gt;
&lt;li&gt;Data Layer – Handles API calls, implements repository (Mpesa Data Source, Payment Model).&lt;/li&gt;
&lt;li&gt;Presentation Layer – Manages UI &amp;amp; state (Payment Provider, Payment Screen).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Why It Matters?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier maintenance &amp;amp; debugging&lt;/li&gt;
&lt;li&gt;Supports scalability (e.g., adding payment history)&lt;/li&gt;
&lt;li&gt;Improved testability &amp;amp; flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's implement these layers step by step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a new Flutter project
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter create mpesa_app
cd mpesa_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Add required dependencies Open pubspec.yaml and add:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  mpesa_flutter_plugin: 
  flutter_svg: 
  provider: 
  get_it: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter pub get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up our project structure. Here's how we'll organize our files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mpesa_clean_app/
├── lib/
│   ├── core/                                # Core utilities
│   │   └── di/
│   │       └── injection_container.dart     # Dependency injection setup
│   │
│   ├── domain/                              # Domain Layer (Business Rules)
│   │   ├── entities/                        # Enterprise business rules
│   │   │   └── payment.dart                 # Core payment entity
│   │   ├── repositories/                    # Repository interfaces
│   │   │   └── payment_repository.dart      # Payment repository contract
│   │   └── usecases/                        # Application business rules
│   │       └── process_payment_usecase.dart # Payment processing logic
│   │
│   ├── data/                                # Data Layer (Implementation)
│   │   ├── datasources/                     # Data providers
│   │   │   └── mpesa_data_source.dart       # M-PESA API integration
│   │   ├── models/                          # Data models that extend entities
│   │   │   └── payment_model.dart           # Payment data model
│   │   └── repositories/                    # Repository implementations
│   │       └── payment_repository_impl.dart # Payment repository implementation
│   │
│   ├── presentation/                        # Presentation Layer (UI)
│   │   ├── pages/
│   │   │   └── payment_page.dart            # Payment UI screen
│   │   └── providers/
│   │       └── payment_provider.dart        # Payment state management
│   │
│   └── main.dart                            # Application entry point
│
├── assets/
│   └── pesa.jpg                             # M-PESA logo img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Now, let's implement each part of our application from the ground up, following Clean Architecture principles. We'll start with the innermost layer (Domain) and work our way outward.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Domain Layer - The Heart of Our Application
&lt;/h2&gt;

&lt;p&gt;The domain layer contains the core business logic of our application, independent of any external frameworks or implementations.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;1.1 Creating the Payment Entity&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
First, let's define our Payment entity that represents the core business object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/domain/entities/payment.dart

class Payment {
  final String phoneNumber;
  final double amount;
  final String businessCode;
  final String reference;
  final String description;

  Payment({
    required this.phoneNumber,
    required this.amount,
    this.businessCode = "174379",
    this.reference = "Test Payment",
    this.description = "Test Payment",
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This entity contains only the essential properties needed for a payment transaction with M-PESA. It has no dependencies on external frameworks or APIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;1.2 Defining the Repository Interface&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Next, we'll define the repository interface that specifies how our application will interact with data sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/domain/repositories/payment_repository.dart

import '../entities/payment.dart';

abstract class PaymentRepository {
  // Initiate a payment transaction
  Future&amp;lt;PaymentResult&amp;gt; initiatePayment(Payment payment);
}

// Result class to encapsulate the outcome of a payment operation
class PaymentResult {
  final bool success;
  final String message;
  final Map&amp;lt;String, dynamic&amp;gt;? data;

  PaymentResult({
    required this.success,
    required this.message,
    this.data,
  });

  // Factory constructor for success case
  factory PaymentResult.success(String message, {Map&amp;lt;String, dynamic&amp;gt;? data}) {
    return PaymentResult(
      success: true,
      message: message,
      data: data,
    );
  }

  // Factory constructor for failure case
  factory PaymentResult.failure(String message) {
    return PaymentResult(
      success: false,
      message: message,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we've included a PaymentResult class to standardize how payment results are communicated throughout our application.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;1.3 Implementing the Use Case&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Now, let's create a use case that orchestrates the business logic for processing a payment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/domain/usecases/process_payment_usecase.dart

import '../entities/payment.dart';
import '../repositories/payment_repository.dart';

class ProcessPaymentUseCase {
  final PaymentRepository repository;

  ProcessPaymentUseCase(this.repository);

  // Execute the use case with the given parameters
  Future&amp;lt;PaymentResult&amp;gt; execute({
    required String phoneNumber,
    required String amountString,
  }) async {
    // Validate input
    if (phoneNumber.isEmpty || amountString.isEmpty) {
      return PaymentResult.failure("Phone number and amount are required");
    }

    if (!phoneNumber.startsWith('254') || phoneNumber.length != 12) {
      return PaymentResult.failure("Please enter a valid phone number starting with 254");
    }

    double? amount = double.tryParse(amountString);
    if (amount == null || amount &amp;lt;= 0) {
      return PaymentResult.failure("Please enter a valid amount greater than 0");
    }

    // Create payment entity and process it through the repository
    final payment = Payment(
      phoneNumber: phoneNumber,
      amount: amount,
    );

    return await repository.initiatePayment(payment);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This use case contains validation logic and coordinates between the UI and data layers. It depends only on domain entities and interfaces, not on concrete implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Data Layer - Connecting to the Outside World&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The data layer implements the repository interfaces defined in the domain layer and handles external data sources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;2.1 Creating the Payment Model&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
First, let's create a model that extends our domain entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/data/models/payment_model.dart

import '../../domain/entities/payment.dart';

class PaymentModel extends Payment {
  PaymentModel({
    required String phoneNumber,
    required double amount,
    String businessCode = "174379",
    String reference = "Test Payment",
    String description = "Test Payment",
  }) : super(
          phoneNumber: phoneNumber,
          amount: amount,
          businessCode: businessCode,
          reference: reference,
          description: description,
        );

  // Convert model to map for API requests
  Map&amp;lt;String, dynamic&amp;gt; toMap() {
    return {
      'phoneNumber': phoneNumber,
      'amount': amount,
      'businessShortCode': businessCode,
      'accountReference': reference,
      'transactionDesc': description,
    };
  }

  // Create model from map (e.g., from API responses)
  factory PaymentModel.fromMap(Map&amp;lt;String, dynamic&amp;gt; map) {
    return PaymentModel(
      phoneNumber: map['phoneNumber'],
      amount: map['amount'],
      businessCode: map['businessShortCode'] ?? "174379",
      reference: map['accountReference'] ?? "Test Payment",
      description: map['transactionDesc'] ?? "Test Payment",
    );
  }

  // Create model from domain entity
  factory PaymentModel.fromEntity(Payment payment) {
    return PaymentModel(
      phoneNumber: payment.phoneNumber,
      amount: payment.amount,
      businessCode: payment.businessCode,
      reference: payment.reference,
      description: payment.description,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This model extends our Payment entity with additional functionality for data transformation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;2.2 Implementing the M-PESA Data Source&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Now, let's create a data source that handles the actual API calls to M-PESA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/data/datasources/mpesa_data_source.dart

import 'package:flutter/material.dart';
import 'package:mpesa_flutter_plugin/initializer.dart';
import 'package:mpesa_flutter_plugin/payment_enums.dart';

import '../models/payment_model.dart';

abstract class MpesaDataSource {
  Future&amp;lt;Map&amp;lt;String, dynamic&amp;gt;&amp;gt; initiateSTKPush(PaymentModel payment);
}

class MpesaDataSourceImpl implements MpesaDataSource {
  // Initialize M-PESA SDK
  void initialize() {
    MpesaFlutterPlugin.setConsumerKey(
        "input ur consumer key here");
    MpesaFlutterPlugin.setConsumerSecret(
        "input ur consumer secret key here");
  }

  @override
  Future&amp;lt;Map&amp;lt;String, dynamic&amp;gt;&amp;gt; initiateSTKPush(PaymentModel payment) async {
    try {
      final result = await MpesaFlutterPlugin.initializeMpesaSTKPush(
          businessShortCode: payment.businessCode,
          transactionType: TransactionType.CustomerPayBillOnline,
          amount: payment.amount,
          partyA: payment.phoneNumber,
          partyB: payment.businessCode,
          callBackURL: Uri.parse(
              "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"),
          accountReference: payment.reference,
          phoneNumber: payment.phoneNumber,
          baseUri: Uri.parse(
              "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"),
          transactionDesc: payment.description,
          passKey:
              "input ur passkey here");

      debugPrint("Transaction Result: $result");
      return {
        'success': true,
        'message': 'STK Push sent successfully',
        'data': result,
      };
    } catch (e) {
      debugPrint("Exception: $e");
      return {
        'success': false,
        'message': e.toString(),
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class uses the mpesa_flutter_plugin to interact with the M-PESA API. It's responsible for the actual implementation details of the API calls.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;2.3 Implementing the Repository&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Now, let's implement the repository interface we defined in the domain layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/data/repositories/payment_repository_impl.dart

import '../../domain/entities/payment.dart';
import '../../domain/repositories/payment_repository.dart';
import '../datasources/mpesa_data_source.dart';
import '../models/payment_model.dart';

class PaymentRepositoryImpl implements PaymentRepository {
  final MpesaDataSource dataSource;

  PaymentRepositoryImpl(this.dataSource);

  @override
  Future&amp;lt;PaymentResult&amp;gt; initiatePayment(Payment payment) async {
    try {
      // Convert domain entity to data model
      final paymentModel = PaymentModel.fromEntity(payment);

      // Call data source
      final result = await dataSource.initiateSTKPush(paymentModel);

      if (result['success']) {
        return PaymentResult.success(
          'Please check your phone for the STK push prompt',
          data: result['data'],
        );
      } else {
        return PaymentResult.failure(result['message']);
      }
    } catch (e) {
      return PaymentResult.failure('An error occurred: ${e.toString()}');
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This repository implementation coordinates between the data source and the domain layer, converting between domain entities and data models.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Presentation Layer - Creating the User Interface&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The presentation layer handles UI components and state management.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;3.1 Implementing the Payment Provider&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Let's create a provider for managing payment state in the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/presentation/providers/payment_provider.dart

import 'package:flutter/material.dart';
import '../../domain/usecases/process_payment_usecase.dart';

class PaymentProvider extends ChangeNotifier {
  final ProcessPaymentUseCase _processPaymentUseCase;

  PaymentProvider(this._processPaymentUseCase);

  bool _isLoading = false;
  String? _errorMessage;
  String? _successMessage;

  // Getters
  bool get isLoading =&amp;gt; _isLoading;
  String? get errorMessage =&amp;gt; _errorMessage;
  String? get successMessage =&amp;gt; _successMessage;

  // Reset state
  void resetState() {
    _errorMessage = null;
    _successMessage = null;
    notifyListeners();
  }

  // Process payment
  Future&amp;lt;bool&amp;gt; processPayment({
    required String phoneNumber,
    required String amount,
  }) async {
    resetState();

    _isLoading = true;
    notifyListeners();

    try {
      // Execute the use case
      final result = await _processPaymentUseCase.execute(
        phoneNumber: phoneNumber,
        amountString: amount,
      );

      _isLoading = false;

      if (result.success) {
        _successMessage = result.message;
        notifyListeners();
        return true;
      } else {
        _errorMessage = result.message;
        notifyListeners();
        return false;
      }
    } catch (e) {
      _isLoading = false;
      _errorMessage = "Unexpected error: ${e.toString()}";
      notifyListeners();
      return false;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provider manages the state of our payment form and communicates with the domain layer through the use case.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;3.2 Building the Payment Page&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Let's create the UI component that displays the payment form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/presentation/pages/payment_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/payment_provider.dart';

class PaymentPage extends StatefulWidget {
  const PaymentPage({Key? key}) : super(key: key);

  @override
  State&amp;lt;PaymentPage&amp;gt; createState() =&amp;gt; _PaymentPageState();
}

class _PaymentPageState extends State&amp;lt;PaymentPage&amp;gt; {
  late TextEditingController _phoneController;
  late TextEditingController _amountController;
  final GlobalKey&amp;lt;FormState&amp;gt; _formKey = GlobalKey&amp;lt;FormState&amp;gt;();

  @override
  void initState() {
    super.initState();
    _phoneController = TextEditingController();
    _amountController = TextEditingController();
  }

  @override
  void dispose() {
    _phoneController.dispose();
    _amountController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer&amp;lt;PaymentProvider&amp;gt;(
      builder: (context, provider, child) {
        // Listen for messages from the provider
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (provider.errorMessage != null) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(provider.errorMessage!),
                backgroundColor: Colors.red,
                duration: const Duration(seconds: 5),
              ),
            );
            provider.resetState();
          }

          if (provider.successMessage != null) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(provider.successMessage!),
                backgroundColor: Colors.green,
                duration: const Duration(seconds: 5),
              ),
            );
            provider.resetState();
          }
        });

        return Scaffold(
          appBar: AppBar(
            title: const Text(
              'M-PESA Payment',
              style: TextStyle(color: Colors.white),
            ),
            centerTitle: true,
          ),
          body: SingleChildScrollView(
            padding: const EdgeInsets.all(16.0),
            child: Form(
              key: _formKey,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: &amp;lt;Widget&amp;gt;[
                  Image.asset(
                    'assets/pesa.jpg',
                    height: 150,
                    width: 150,
                  ),
                  const SizedBox(height: 30.0),
                  TextFormField(
                    controller: _phoneController,
                    keyboardType: TextInputType.phone,
                    decoration: const InputDecoration(
                      labelText: 'Phone Number',
                      hintText: 'Enter phone no (254XXX)',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.phone),
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Please enter a phone number';
                      }
                      if (!value.startsWith('254') || value.length != 12) {
                        return 'Please enter a valid phone number starting with 254';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 20.0),
                  TextFormField(
                    controller: _amountController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: 'Amount (KES)',
                      hintText: 'Enter amount',
                      border: OutlineInputBorder(),
                      prefixIcon: Icon(Icons.money),
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Please enter an amount';
                      }
                      if (double.tryParse(value) == null) {
                        return 'Please enter a valid amount';
                      }
                      if (double.parse(value) &amp;lt;= 0) {
                        return 'Amount must be greater than 0';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 30.0),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(
                            horizontal: 40, vertical: 15),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(30),
                        ),
                      ),
                      icon: provider.isLoading
                          ? const SizedBox(
                              width: 20,
                              height: 20,
                              child: CircularProgressIndicator(
                                color: Colors.white,
                                strokeWidth: 2,
                              ),
                            )
                          : const Icon(Icons.payment),
                      label: Text(
                        provider.isLoading ? 'Processing...' : 'Pay Now',
                        style: const TextStyle(fontSize: 18),
                      ),
                      onPressed: provider.isLoading
                          ? null
                          : () {
                              if (_formKey.currentState!.validate()) {
                                provider.processPayment(
                                  phoneNumber: _phoneController.text,
                                  amount: _amountController.text,
                                );
                              }
                            },
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This page displays a form for entering payment details and shows loading, error, and success states.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Core Layer - Setting Up Dependency Injection&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, let's set up dependency injection to wire everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/core/di/injection_container.dart

import 'package:get_it/get_it.dart';

import '../../data/datasources/mpesa_data_source.dart';
import '../../data/repositories/payment_repository_impl.dart';
import '../../domain/repositories/payment_repository.dart';
import '../../domain/usecases/process_payment_usecase.dart';
import '../../presentation/providers/payment_provider.dart';

final sl = GetIt.instance;

void init() {
  // Presentation layer
  sl.registerFactory(
    () =&amp;gt; PaymentProvider(sl()),
  );

  // Domain layer - Use cases
  sl.registerLazySingleton(() =&amp;gt; ProcessPaymentUseCase(sl()));

  // Domain layer - Repositories
  sl.registerLazySingleton&amp;lt;PaymentRepository&amp;gt;(
    () =&amp;gt; PaymentRepositoryImpl(sl()),
  );

  // Data layer - Data sources
  sl.registerLazySingleton&amp;lt;MpesaDataSource&amp;gt;(
    () =&amp;gt; MpesaDataSourceImpl(),
  );

  // Initialize M-PESA
  final mpesaDataSource = sl&amp;lt;MpesaDataSource&amp;gt;() as MpesaDataSourceImpl;
  mpesaDataSource.initialize();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're using the get_it package for service location, registering our dependencies in a way that follows our clean architecture layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;&lt;strong&gt;5. Application Entry Point&lt;/strong&gt;&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, let's tie everything together in our main.dart file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
import 'core/di/injection_container.dart' as di;
import 'presentation/pages/payment_page.dart';
import 'presentation/providers/payment_provider.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Enable performance profiling if needed
  debugProfileBuildsEnabled = true;
  debugProfilePaintsEnabled = true;

  // Initialize dependency injection
  di.init();

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) =&amp;gt; di.sl&amp;lt;PaymentProvider&amp;gt;(),
        ),
      ],
      child: MaterialApp(
        title: 'M-PESA Payment',
        theme: ThemeData(
          primarySwatch: Colors.green,
        ),
        home: const PaymentPage(),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets up our application, initializes dependencies, and provides our state management to the widget tree.&lt;/p&gt;

&lt;p&gt;Now, let's test our application. Open the terminal and run the following command to verify that prompts are being sent correctly and responses are processed as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Building an M-PESA payment application using Clean Architecture provides not just a functional app, but a solid foundation for future development. The separation of concerns allows for easier testing, maintenance, and evolution of the codebase.&lt;br&gt;
By following this tutorial, you've learned how to structure a Flutter application with Clean Architecture principles, implement M-PESA integration, and create a seamless payment experience for your users.&lt;/p&gt;

&lt;p&gt;Remember that good architecture isn't about following rules blindly, but about creating a codebase that's adaptable to change and easy to understand. As your application grows, the investment in clean architecture will pay dividends in reduced technical debt and increased development speed.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mpesa</category>
      <category>cleancoding</category>
      <category>dart</category>
    </item>
    <item>
      <title>Flutter_dotenv Mastery: From Setup to Production</title>
      <dc:creator>joelytic s </dc:creator>
      <pubDate>Thu, 29 Aug 2024 19:56:57 +0000</pubDate>
      <link>https://dev.to/joel207/flutterdotenv-mastery-from-setup-to-production-1jpo</link>
      <guid>https://dev.to/joel207/flutterdotenv-mastery-from-setup-to-production-1jpo</guid>
      <description>&lt;p&gt;In the world of Flutter development, managing environment-specific configurations and sensitive data is crucial. The flutter_dotenv package offers a robust solution to this challenge, allowing developers to externalize configuration details from their codebase. This comprehensive guide will take you from the basics to advanced usage, ensuring you're equipped to leverage flutter_dotenv effectively in your projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;[1. Installation and Setup]&lt;br&gt;
[2. Basic Configuration]&lt;br&gt;
[3. Advanced Usage]&lt;br&gt;
   . Custom Environment Files&lt;br&gt;
   . Type-safe Configuration&lt;br&gt;
   . Flavor-specific Environments&lt;br&gt;
[4. Common Errors and Troubleshooting&lt;br&gt;
   .env File Not Found&lt;br&gt;
   . Unable to Load .env File&lt;br&gt;
   . Environment Variable Not Found&lt;br&gt;
   . Incorrect Variable Types]&lt;br&gt;
[5. Best Practices and Security Considerations]&lt;br&gt;
[6. Integration with CI/CD]&lt;br&gt;
[7. Performance Optimization]&lt;br&gt;
[. Conclusion]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Installation and Setup&lt;/strong&gt;&lt;br&gt;
First, add &lt;em&gt;flutter_dotenv&lt;/em&gt; to your &lt;em&gt;pubspec.yaml&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  flutter_dotenv: ^5.0.2 # Check for the latest version on [pub.dev](https://pub.dev/packages/flutter_dotenv)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run flutter pub get to install the package.&lt;br&gt;
Create a &lt;em&gt;.env&lt;/em&gt; file in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_URL=https://api.example.com
API_KEY=your_secret_api_key
DEBUG_MODE=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;em&gt;.env&lt;/em&gt; file to your &lt;em&gt;pubspec.yaml&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter:
  assets:
    - .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Basic Configuration&lt;/strong&gt;&lt;br&gt;
In your &lt;em&gt;main.dar_t, load the _.env&lt;/em&gt; file before running your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:flutter_dotenv/flutter_dotenv.dart';

Future&amp;lt;void&amp;gt; main() async {
  await dotenv.load(fileName: ".env");
  runApp(MyApp());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can access environment variables in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String apiUrl = dotenv.env['API_URL'] ?? 'https://default-api.com';
String apiKey = dotenv.env['API_KEY'] ?? '';
bool debugMode = dotenv.env['DEBUG_MODE'] == 'true';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Advanced Usage&lt;br&gt;
3.1 Custom Environment Files&lt;/strong&gt;&lt;br&gt;
For different environments (dev, staging, prod), create separate &lt;em&gt;.env&lt;/em&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (kReleaseMode) {
  await dotenv.load(fileName: ".env.production");
} else {
  await dotenv.load(fileName: ".env.development");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.2 Type-safe Configuration&lt;/strong&gt;&lt;br&gt;
Create a configuration class for type safety:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02ahv5kvdef31l2gg235.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02ahv5kvdef31l2gg235.png" alt="Type-safe Configuration" width="525" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3 Flavor-specific Environments&lt;/strong&gt;&lt;br&gt;
For different app flavors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; main() async {
  const flavor = String.fromEnvironment('FLAVOR');
  await dotenv.load(fileName: ".env.$flavor");
  runApp(MyApp());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run with: &lt;em&gt;flutter run --dart-define=FLAVOR=dev&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Common Errors and Troubleshooting&lt;br&gt;
4.1 .env File Not Found&lt;br&gt;
Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Unable to load asset: .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solutions:&lt;/p&gt;

&lt;p&gt;. Verify file existence and path&lt;br&gt;
. Check &lt;em&gt;pubspec.yaml&lt;/em&gt; asset declaration&lt;br&gt;
. Run &lt;em&gt;flutter clean&lt;/em&gt; and &lt;em&gt;flutter pub get&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.2 Unable to Load .env File&lt;br&gt;
Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Unable to load dot env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solutions:&lt;/p&gt;

&lt;p&gt;. Check file permissions&lt;br&gt;
. Ensure UTF-8 encoding without BOM&lt;br&gt;
. Remove comments and empty lines&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.3 Environment Variable Not Found&lt;br&gt;
Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Null check operator used on a null value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solutions:&lt;/p&gt;

&lt;p&gt;. Verify variable names&lt;br&gt;
. Use null-aware operators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dartCopyString? apiKey = dotenv.env['API_KEY'];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;. Provide default values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dartCopyString apiKey = dotenv.env['API_KEY'] ?? 'default_key';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;**4.4 Incorrect Variable Types&lt;/em&gt;*&lt;br&gt;
4.4 Incorrect Variable Types&lt;br&gt;
Problem: &lt;strong&gt;All variables are strings by default.&lt;br&gt;
**Solution:&lt;/strong&gt; Parse values explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dartCopybool debugMode = dotenv.env['DEBUG_MODE']?.toLowerCase() == 'true';
int maxRetries = int.tryParse(dotenv.env['MAX_RETRIES'] ?? '') ?? 3;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Best Practices and Security Considerations&lt;/strong&gt;&lt;br&gt;
  &lt;strong&gt;1. Git Ignore:&lt;/strong&gt; Add &lt;em&gt;.env&lt;/em&gt; to .gitignore&lt;br&gt;
  &lt;strong&gt;2. Template File:&lt;/strong&gt; Provide &lt;em&gt;.env.example&lt;/em&gt;&lt;br&gt;
  &lt;strong&gt;3. Validation:&lt;/strong&gt; Implement startup checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dartCopyvoid validateEnv() {
  final requiredKeys = ['API_KEY', 'API_URL'];
  final missingKeys = requiredKeys.where((key) =&amp;gt; dotenv.env[key] == null);
  if (missingKeys.isNotEmpty) {
    throw Exception('Missing required env keys: $missingKeys');
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4. _Encryption:_ Consider encrypting sensitive values
5. _Access Control:_ Limit access to production _.env_ files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;6. Integration with CI/CD&lt;/strong&gt;&lt;br&gt;
For CI/CD pipelines, inject environment variables during build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwtxgn32xlxl6fz0zsx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwtxgn32xlxl6fz0zsx2.png" alt="ci/cd" width="429" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Performance Optimization&lt;/strong&gt;&lt;br&gt;
Loading _.env _ files can impact startup time. Optimize by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minimizing file size&lt;/li&gt;
&lt;li&gt;Caching parsed values:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17alphsvjdmevclmr2tv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17alphsvjdmevclmr2tv.png" alt="Bar chart showing monthly sales trends" width="366" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Mastering &lt;em&gt;flutter_dotenv&lt;/em&gt; is essential for building scalable, secure, and maintainable Flutter applications. By following this guide, you've learned how to effectively manage environment variables, handle common issues, and implement best practices. Remember, proper configuration management is crucial as your project grows and moves through different environments. Keep your sensitive data secure, your configurations flexible, and your code clean with &lt;em&gt;flutter_dotenv&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
