DEV Community

Cover image for Building a Flutter Shopping App from Scratch — Part 1: Scaffolding Models, Providers, and Screens
Yadnesh Teli
Yadnesh Teli

Posted on AI-assisted

Building a Flutter Shopping App from Scratch — Part 1: Scaffolding Models, Providers, and Screens

Series 1 of 2 — Part 1 of 5

Building a Flutter Shopping App from Scratch — Part 1: Scaffolding Models, Providers, and Screens

Series: Building a Flutter Shopping App — From Zero to Architecture

Every Flutter developer I know has at some point just started writing code without thinking too much about architecture. I was no different. When my internship mentor gave me a shopping app to build as a learning project, I opened VS Code and started typing.

This post covers what I built on day one — the models, providers, and screens — and why I made the choices I made, even if some of them turned out to be wrong.

The project brief

A standard e-commerce shopping app. Products, categories, a cart, a favourites list, and user authentication. Nothing groundbreaking, but enough to force you to think about state, data flow, and UI structure.

At this point in my Flutter journey I had a basic understanding of widgets and StatefulWidgets. I had read about Provider but not really used it properly. I did not know what MVVM was.

Starting with models

The first thing I did was define my data structures. A product has a name, price, image URL, and category. A category is just an id and a label.

class ProductModel {
  final String id;
  final String name;
  final double price;
  final String imageUrl;
  final String categoryId;

  ProductModel({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
    required this.categoryId,
  });
}
Enter fullscreen mode Exit fullscreen mode

Clean enough to start with. At this stage I was not thinking about fromJson or toJson — that would come back to bite me later when I integrated Firestore.

Building the providers

I used Riverpod for state management. I created providers for products, categories, cart, favourites, and search. Each was a StateNotifier that held a list and exposed methods to add, remove, and filter items.

The cart provider looked reasonable at first. But there was a problem I did not see yet — it had no concept of the current user. The cart data was global. If two users logged in on the same device, they would share the same cart. This is the kind of issue you only discover when you try to add authentication.

Setting up screens

I built three screens: HomeScreen, CartScreen, and FavouritesScreen. The HomeScreen had a product grid, category filters, and a search bar with debounce. The CartScreen showed cart items with quantities. The FavouritesScreen was a responsive grid of saved products.

I also added Device Preview at this stage, which turned out to be a good decision. It caught several responsive layout bugs early without needing to switch simulators.

What I got right

  • Using Riverpod from the start: Even my naive providers were easier to manage than raw setState.
  • Separating models early: Gave me a consistent data layer before I understood the full architecture.
  • Adding Device Preview early: Surfaced layout bugs while they were still cheap to fix.

What I got wrong

  • Global user state: Cart and favourites providers had no concept of the current user — state was global.
  • Leaking business logic: Business logic was leaking into the UI layer — screens called provider methods with too much knowledge of storage internals.
  • Tangled data sources: No repositories. API calls, local storage reads, and state updates were all tangled inside the same providers.

What is coming next

In Part 2 I add authentication — Firebase login, signup, Google Sign-In, and the surprisingly tricky problem of merging accounts when a user signs up with email and then tries to log in with Google using the same address.

Next: Part 2 — Adding Firebase Auth, Google Sign-In, and Account Merging

Top comments (0)