Skip to content
categoryMobile Development

Setting Up a Structured Flutter App Using Provider: A Step-by-Step Guide

Ayyaz Zafar3 min read

When structuring a Flutter project that uses the provider package for state management, it's crucial to ensure that your codebase is organized, scalable, and maintainable. Here's a suggested directory structure for a professional Flutter project using the Provider pattern:

Text
lib/|-- main.dart|-- app.dart|-- utils/|   |-- constants.dart|   |-- theme.dart|-- models/|   |-- user.dart|   |-- order.dart|-- providers/|   |-- user_provider.dart|   |-- order_provider.dart|-- services/|   |-- api_service.dart|   |-- auth_service.dart|-- screens/|   |-- home/|   |   |-- home_screen.dart|   |   |-- home_viewmodel.dart|   |-- profile/|   |   |-- profile_screen.dart|   |   |-- profile_viewmodel.dart|-- widgets/|   |-- custom_button.dart|   |-- custom_textfield.dart|-- config/|   |-- routes.dart|   |-- dependencies.dart

Explanation:

  1. main.dart: The entry point of the Flutter app.
  2. app.dart: A separate file for the MaterialApp to keep the main.dart file clean. This is where you can set up theme, routes, initial screen, etc.
  3. utils: General utility files like constants, themes, helper functions.
  4. models: Contains data models, typically Plain Old Dart Objects (PODO) that might be used to represent the shape of JSON data, database rows, etc.
  5. providers: This is where all the Providers for state management using the provider package will reside.
  6. services: Services like API calls, database handlers, authentication, etc.
  7. screens: Each screen in the app gets its own sub-directory which might include:
    • _screen.dart: The widget file for the screen itself.
    • _viewmodel.dart: Logic and data that the screen needs, typically using ChangeNotifier.
  8. widgets: Common or reusable widgets for the entire app.
  9. config: Configuration-related files.

    • routes.dart: Defines named routes for navigation.
    • dependencies.dart: If you're using a dependency injection package like get_it, you can set up your service locators here.

When starting a new project, it's often helpful to use code generation tools or project templates to expedite the setup of such structures. This directory structure is just a suggestion; the "best" structure can vary depending on team preferences, project requirements, and other factors.

Creating an entire basic app with every file mentioned is a considerable task, but I'll provide you with a simplified version of each file to get you started. For simplicity, let's say our app displays a list of users.

1. main.dart

JavaScript
import 'package:flutter/material.dart';import 'app.dart';void main() => runApp(MyApp());

2. app.dart

JavaScript
import 'package:flutter/material.dart';import 'config/routes.dart';import 'screens/home/home_screen.dart';import 'utils/theme.dart';class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Provider Demo',      theme: appTheme,      routes: routes,      home: HomeScreen(),    );  }}

3. utils/constants.dart

JavaScript
const String API_ENDPOINT = "https://api.example.com";

4. utils/theme.dart

JavaScript
import 'package:flutter/material.dart';final ThemeData appTheme = ThemeData(  primarySwatch: Colors.blue,);

5. models/user.dart

JavaScript
class User {  final String id;  final String name;  User({required this.id, required this.name});}

6. providers/user_provider.dart

TSX
import 'package:flutter/material.dart';import 'models/user.dart';import 'services/api_service.dart';class UserProvider with ChangeNotifier {  List<User> _users = [];  List<User> get users => _users;  fetchUsers() async {    _users = await ApiService.fetchUsers();    notifyListeners();  }}

7. services/api_service.dart

TSX
import '../models/user.dart';import '../utils/constants.dart';class ApiService {  static Future<List<User>> fetchUsers() async {    // Normally we'd call the API here, but for simplicity, we'll return a dummy list.    return [      User(id: '1', name: 'John'),      User(id: '2', name: 'Doe'),    ];  }}

8. screens/home/home_screen.dart

TSX
import 'package:flutter/material.dart';import 'package:provider/provider.dart';import '../../providers/user_provider.dart';class HomeScreen extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('Users')),      body: Consumer<UserProvider>(        builder: (context, userProvider, child) {          return ListView.builder(            itemCount: userProvider.users.length,            itemBuilder: (context, index) {              return ListTile(title: Text(userProvider.users[index].name));            },          );        },      ),      floatingActionButton: FloatingActionButton(        onPressed: () => Provider.of<UserProvider>(context, listen: false).fetchUsers(),        child: Icon(Icons.refresh),      ),    );  }}

9. widgets/custom_button.dart (example widget)

JavaScript
import 'package:flutter/material.dart';class CustomButton extends StatelessWidget {  final String text;  final VoidCallback onPressed;  CustomButton({required this.text, required this.onPressed});  @override  Widget build(BuildContext context) {    return ElevatedButton(      onPressed: onPressed,      child: Text(text),    );  }}

10. config/routes.dart

JavaScript
import 'package:flutter/material.dart';import '../screens/home/home_screen.dart';final Map<String, WidgetBuilder> routes = {  '/': (context) => HomeScreen(),};

11. services/auth_service.dart (example service)

JavaScript
class AuthService {  static Future<bool> isAuthenticated() async {    return true; // This is just a dummy function for the sake of example.  }}

For brevity, some files such as profile_screen.dart, profile_viewmodel.dart, order.dart, order_provider.dart, and custom_textfield.dart were not included. Also, this is a very basic example, and in a real-world scenario, you'd have more comprehensive logic, error handling, more features, etc.

Remember to include the necessary dependencies (flutter/material.dart, provider, etc.) in each file and to integrate the provider package by adding it to your pubspec.yaml file.

Get new tutorials by email

One email when something new is published. Unsubscribe anytime.

Written by Ayyaz Zafar, full-stack developer for 15+ years. Work with me