Flutter Architecture
Tags: flutter, tutorial, mobile, dart
Published: 2020-11-03 17:09:23

Hello reader! Flutter has been growing popular over the years, especially in the mobile field. It's mostly easy to learn and can greatly increase speed of development. There is a posing question though for a beginner in flutter, How do I make my code clean? How do I manage state? How do I structure my code?

In the article I am going to be showing you a way to make your code clean, a structure that is flexible of course, so it can be changed or edited by you. This takes into consideration separation of concerns(which of course is a big issue).

#What we are going to create
To make things extremely simple we are going to be creating an application that uses the openweathermapapi to get weather data and display some of it as text on our screen. The final application is shown below

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kkjutjrh1p0r1mgybjyj.PNG)

#Project Setup
This project is going to have a total of 6 folders in our lib directory
* data
* mixin
* models
* providers
* screens
* utils

For state management we are going to make use of the provider pattern with the provider plugin and dio for http calls. To use this we need to add the dependencies in our pubspec.yaml folder 
Provider [https://pub.dev/packages/provider]
Dio [https://pub.dev/packages/dio]

#The Process
The flow of our application is divided into different classes, each one performing a particular task.
I will just show the code and explain what the class does and at the end of this, I will explain the entire process in detail. 

#The Data classes
In the data folder we are going to create 2 classes `weather_data` and `weather_repo`

>The `weather_data` class is responsible for fetching the weather data from the api, and returning the data to the repo class. 
The `getWeather` method takes in a `dio` parameter that will be parse in by the view `home_screen`. 
When the data has been gotten successfully from the api the weather class feeds the status of the response and the actual data to the `Operation` class and returns an instance of this class to the `Weather Repo`

####Weather data 
```dart

import 'package:dio/dio.dart';
import 'package:weather_art/models/country_response_model.dart';
import 'package:weather_art/utils/operation.dart';

class WeatherData{
  Future<Operation> getWeather(Dio dio) async{

    try{
      var response = await dio.get(
          'http://api.openweathermap.org/data/2.5/weather?q=lagos&appid={api_key}',

      ).timeout(Duration(minutes: 2), onTimeout: () async{
        return Response(
            data: {"message": "Connection Timed out. Please try again"},
            statusCode: 408);
      }).catchError((error) {
        return Response(
            data: {"message": "Error occurred while connecting to server"},
            statusCode: 508);
      });

   
      if(response.statusCode == 508 || response.statusCode == 408){
        return Operation(response.statusCode, response.data);
      }else{

        WeatherResponse data = WeatherResponse.fromJson(response.data);

        return Operation(response.statusCode, data);
      }

    }catch(err){
       //catch err
      }
    }
  }

}

final countryData = WeatherData();
```

The `weather_repo` class is what calls the data class and waits for it to return data.
####Weather Repo

```dart
import 'package:dio/dio.dart';
import 'package:weather_art/data/weather_data.dart';
import 'package:weather_art/utils/operation.dart';

class _WeatherRepo{
   getWeatherData(Dio dio, OperationCompleted countryDataCompleted){
      countryData.getWeather(dio).then((data) => countryDataCompleted(data));
   }
}

_WeatherRepo countryRepo = _WeatherRepo();
```
#The Mixin classes
In the mixin folder we are going to create one class and call it `home_helper`. This class works with the provider to change the state of our view depending on the data gotten. This makes sure that the code for changing state is separated from the view itself and reduces the logic done in the view
```dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:weather_art/data/weather_repo.dart';
import 'package:weather_art/models/country_response_model.dart';
import 'package:weather_art/providers/home_provider.dart';
import 'package:weather_art/utils/operation.dart';

mixin HomeHelper{
  BuildContext _authContext;

  doGetWeather(Dio dio, BuildContext context){
    _authContext = context;
    Provider.of<HomeProvider>(_authContext, listen: false).updateIsLoading(true);
    weatherRepo.getWeatherData(dio, _weatherDataCompleted);
  }

  _weatherDataCompleted(Operation operation){

    if(operation.code == 408 || operation.code == 508){
      //handle time out
      Provider.of<HomeProvider>(_authContext, listen: false).updateIsLoading(false);
      print('connection timed out');
    }else{
      WeatherResponse weatherResponse = operation.result;
      Provider.of<HomeProvider>(_authContext, listen: false).updateWeather(weatherResponse);

      Provider.of<HomeProvider>(_authContext, listen: false).updateIsLoading(false);

    }
  }
}
```
#The model classes
In this folder we will create our weather model 

```dart
// To parse this JSON data, do
//
//     final weatherResponse = weatherResponseFromJson(jsonString);

import 'dart:convert';

WeatherResponse weatherResponseFromJson(String str) => WeatherResponse.fromJson(json.decode(str));

String weatherResponseToJson(WeatherResponse data) => json.encode(data.toJson());

class WeatherResponse {
  WeatherResponse({
    this.weather,
    this.main,
 
  });

  List<Weather> weather;
  Main main;


  factory WeatherResponse.fromJson(Map<String, dynamic> json) => WeatherResponse(
    coord: Coord.fromJson(json["coord"]),
    weather: List<Weather>.from(json["weather"].map((x) => Weather.fromJson(x))),
    main: Main.fromJson(json["main"]),
    
  );

  Map<String, dynamic> toJson() => {
    "coord": coord.toJson(),
    "weather": List<dynamic>.from(weather.map((x) => x.toJson())),
    "main": main.toJson(),
   
  };
}

class Main {
  Main({
    this.temp,
   
  });

  double temp;


  factory Main.fromJson(Map<String, dynamic> json) => Main(
    temp: json["temp"].toDouble(),
   
  );

  Map<String, dynamic> toJson() => {
    "temp": temp,
  };
}

class Weather {
  Weather({
    this.main,
    this.description,
  });
  String main;
  String description;

  factory Weather.fromJson(Map<String, dynamic> json) => Weather(
  
    main: json["main"],
    description: json["description"],
   
  );

  Map<String, dynamic> toJson() => {
    "main": main,
    "description": description,
   
  };
}

```

#The Provider Folder
We create two files here `HomeProvider`  and `AppProvider`

####Home Provider
```dart
import 'package:flutter/foundation.dart';
import 'package:weather_art/models/country_response_model.dart';

class HomeProvider extends ChangeNotifier{
  bool isLoading = false;
  List<WeatherResponseModel> weatherList = [];
  WeatherResponseModel weatherResponse;

  void updateIsLoading(bool isLoadingGotten){
    isLoading = isLoadingGotten;
    notifyListeners();
  }

  void updateWeather(WeatherResponseModel weatherResponseGotten){
    weatherResponse = weatherResponseGotten;
  }
}
```

####App Provider
```dart
import 'package:dio/dio.dart';

class AppProvider{
  Dio dio = Dio();

}
```

#The Screens Folder
This is where our view is located, we create a file here and call it `home_screen`
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:weather_art/mixin/home_helper.dart';
import 'package:weather_art/providers/app_provider.dart';
import 'package:weather_art/providers/home_provider.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with HomeHelper{

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      doGetWeather(
          Provider.of<AppProvider>(context, listen: false).dio,
          context
      );
    });

  }
  @override
  Widget build(BuildContext context) {
    return Consumer(
        builder: (BuildContext context, HomeProvider homeProvider, Widget child){
          return Scaffold(
            body: Container(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: decideLayout(homeProvider),
            ),
          );
        }
    );
  }

  Widget decideLayout(HomeProvider homeProvider){
    if(homeProvider.isLoading){
      return Center(
        child: CircularProgressIndicator(),
      );
    }else if(homeProvider.isLoading == false && homeProvider.weatherResponse == null){
      return Center(
        child: Text(
          'Null',
          style: TextStyle(
              fontSize: 14
          ),
        ),
      );
    }else{
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Weather in Lagos',
            style: TextStyle(
                fontSize: 18,
              fontWeight: FontWeight.bold
            ),
          ),
          Text(
            'Looks Like ${homeProvider.weatherResponse.weather[0].main}',
            style: TextStyle(
                fontSize: 14
            ),
          ),

          Text(
            'Description ${homeProvider.weatherResponse.weather[0].description}',
            style: TextStyle(
                fontSize: 14
            ),
          ),

          Text(
            'Temp ${homeProvider.weatherResponse.main.temp.toString()}',
            style: TextStyle(
                fontSize: 14
            ),
          ),
        ],
      );
    }
  }
}

```
#The utils folder
This folder contains the class operation. You can just copy and past this in your code. The class accepts two params. The response code and the response data. This class a typedef or callback which is used to check when an operation is completed.

```dart
import 'package:flutter/material.dart';

typedef OperationCompleted(Operation operation);

class Operation {
  final dynamic _result;
  final int code;
  Operation(this.code, this._result);

  bool get succeeded => code >= 200 && code <= 226;
  dynamic get result => _result;
}

```

#The Flow
We have seen all the code and classes involved in this pattern, with some explanation on each, now let me join everything together and paint one picture by following a user's interaction with the application and telling you what happens at each juncture. 
  
A user is clicking around his phone looking for what to do then he says "what's the weather gonna be like today", he then proceeds to open our app. The user is presented with our `home_screen`, our `home_screen` uses a mixin which we called `HomeHelper` and also uses the `HomeProvider` to manage it's own state.

The `isLoading` in the `HomeProvider` class is false so the user sees a circular progress indicating that something is loading. While this is happening the `initState` calls the `doGetWeather` with the needed parameters. The `doGetWeather` is from mixin `HomeHelper`. The `doGetWeather` calls the `weatherRepo.getWeatherData` which is from the `_WeatherRepo`class. This calls the `getWeather` in the `WeatherData` class which is a `Future` so this method expects something to be returned from `getWeather`. `getWeather` is responsible for getting the data from the api and returns `Operation`. `Operation` accepts two parameters which are the status code and the data from the api. When the operation is return from the `getWeather`, the data is then passed backwards.

We should recall that the repo class still expects some data, when the operation class is returned the `.then` in the repo class is called and data is passed to the `countryDataCompleted`, this in turn triggers the `_weatherDataCompleted` in `HomeHelper`. This is where we change our UI and update our data using methods we have created in the HomeProvider.

#Conclusion
Using this architecture we can see that we have succeeded in separating the UI from our logic quite well. Everything is separated making the code easy to read and edit.

####Source Code
[https://github.com/Marcusjnr/weather]