Building Efficient Flutter Web Applications with Stacked

Engineering

Flutter

State Management

Summary

With its strong MVVM-based design, Stacked expands on Flutter's ability to enable developers to build high-performance web applications with a single codebase. Stacked, which is built for scalability, testability, and maintainability, makes state management easier, lowers boilerplate, and promotes clear concern separation.

Key insights:
  • MVVM Architecture: Stacked organizes apps into Model, View, and ViewModel layers, ensuring separation of concerns and maintainable code.

  • Simplified State Management: Centralized logic in ViewModels with reactive updates ensures clean and dynamic user interfaces.

  • Service-Oriented Design: Business logic resides in reusable services, keeping ViewModels lightweight and testable.

  • Dependency Injection: Efficiently manages service instances using tools like get_it, promoting scalability and code consistency.

  • Scalability and Modularity: Ideal for large-scale projects with structured folder systems and adherence to clean architecture principles.

  • Responsive Design: Supports Flutter Web’s flexibility with tools like responsive_builder to adapt layouts across devices.

  • Streamlined Development: Stacked CLI automates setup and creates production-ready projects quickly and efficiently.

  • Web-Specific Adaptations: Addresses unique web challenges such as navigation and responsive interfaces for seamless user experiences.

  • Common Pitfalls: Avoid overloading ViewModels, neglecting reactive updates, or misconfiguring dependency injection for better performance.

  • Project Suitability: Best suited for long-term, complex projects requiring high testability, maintainability, and team collaboration.

Introduction

Google's UI toolkit, Flutter, has expanded beyond mobile devices, enabling programmers to construct slick, effective online apps. Flutter for Web provides robust interaction and device consistency while bridging the gap between native-like experiences and web accessibility with a single codebase. This feature gives developers the means to quickly create scalable, aesthetically pleasing applications by utilizing Flutter's robust widget system and vast library ecosystem.

An opinionated architecture package called Stacked was created especially to tackle the difficulties involved in creating and managing Flutter apps of production quality. Stacked, created by FilledStacks, a group with a wealth of knowledge in Native iOS, Android, Xamarin, and Flutter, captures best practices for maintainability, scalability, and testability. Its MVVM-based architecture streamlines development processes and guarantees that apps are designed to survive the demands of teamwork and real-world deployment. FilledStacks leverages its experience in developing more than 30 applications to provide a framework that satisfies industry requirements.

Because of its many advantages, the Stacked framework is a logical choice for creating Flutter web apps. Because of its well-defined conventions and organized functionality, its emphasis on scalability allows teams to expand their codebases while retaining high productivity. Unit testing is made easier by the testable MVVM architecture, which also promotes trust in state management and application logic. Furthermore, Stacked is built on maintainability, which keeps apps orderly and modular while avoiding the accumulation of technological debt as projects progress. Setting up and managing Stacked projects is now simpler than ever thanks to the addition of a dedicated CLI, which speeds up development even more.

This insight explores how Stacked gives developers the ability to create reliable, effective Flutter web apps. It starts with a synopsis of the framework's design and tenets, emphasizing how it helps create code that is scalable, testable, and manageable. After examining the detailed procedure for establishing and developing a Stacked application, useful advice on utilizing its capabilities for projects that are prepared for production will be provided. Readers will have a thorough grasp of why Stacked is revolutionary for Flutter Web development by the end.

What is Stacked?

The goal of the Stacked architectural framework for Flutter apps is to improve maintainability, testability, and scalability. Stacked uses the Model-View-ViewModel (MVVM) paradigm to solve typical development issues including state management, concern separation, and maintaining a clear user interface. Stacked, created by FilledStacks, is a collection of tools and protocols that help development teams maintain high productivity and code quality. It represents industry expertise in creating large-scale, reliable applications.

1. Understanding MVVM (Model-View-ViewModel)

At the core of Stacked is the MVVM architecture, which structures applications into three main components:

Model: Data and business logic are represented by the model. It manages data retrieval, storage, and updating without relying on the user interface.

View: Stands for the user interface layer, which is in charge of showing the user information and recording their interactions. The View in MVVM is passive and depends on the ViewModel to act.

ViewModel: Serves as a bridge connecting the View and the Model. It includes the display logic, controls the View's state, and updates the Model in response to user input.

Developers may produce applications that are simpler to test, scale, and maintain because of this division of responsibilities.

2. How Stacked Simplifies State Management and UI Structure

Particularly in apps with dynamic user interfaces and frequent data updates, state management in Flutter may get complicated. Stacked makes this easier by:

Centralizing State Logic: To keep Views straightforward and declarative, the ViewModel in Stacked centralizes all state management.

Reducing Boilerplate: Stacked eliminates the need for repetitious code while managing state changes by utilizing built-in capabilities like BaseViewModel.

Enforcing a Clear Architecture: Stacked guarantees a uniform structure throughout the application by following the MVVM principles, which facilitates the onboarding of new developers and the scaling of functionality.

3. Key Components of Stacked:

These are the key components of Stacked: 

The UI layer in Stacked is called View, and it was constructed with Flutter widgets. Rendering the application's interface and assigning logic to the ViewModel are its responsibilities. Most views in Stacked are stateless and only consider display.   

A particular View's logic and state are contained in the ViewModel. Through BaseViewModel extension, developers may leverage Stacked's capabilities to handle asynchronous jobs, failures, and busy states.

Services manage essential operations including local storage, API connectivity, and other backend communications. They let ViewModels concentrate only on business logic by abstracting away the implementation concerns. Usually, services are listed in the Service Locator of the application as dependencies.

The MVVM-based architecture of Stacked guarantees a distinct division of responsibilities, freeing developers to concentrate on certain tasks. Stacked improves the efficiency and sustainability of designing Flutter apps for long-term maintenance by lowering boilerplate, promoting consistent patterns, and offering strong state management capabilities. 

Setting Up Stacked in a Flutter Web Project

Configuring your development environment and organizing your application to fully utilize the advantages of the Stacked architecture are necessary steps in setting up Stacked in a Flutter Web project. Particularly in intricate online applications, this method enables scalability, state management, and a clear division of responsibilities. A thorough guide on using Stacked in a Flutter Web project is provided below, with a focus on the necessary requirements and methodical implementation procedures.

1. Prerequisites for Setup

Make sure Flutter is installed and set up correctly on your computer before beginning the setup. To see how well your Flutter installation is doing, type the flutter doctor -v command into your terminal. Any missing dependencies or setup problems will be found by the results; fixing these is essential before moving forward. Additionally, as this configuration expands on fundamental Flutter concepts and applies them to the web domain, a basic familiarity of Flutter Web is beneficial.

2. Step-by-Step Guide to Setting Up Stacked

Creating a New Flutter Project:To begin, make a new Flutter project that will act as the foundation for your application. To establish a new project directory, use the flutter create <project_name> command. Change <project_name> to the name you want for your application, like todo. To make sure any ensuing commands are run in the appropriate context, use cd <project_name> to navigate into the newly created directory.

The Stacked CLI might help you expedite the setup process if you intend to use the Stacked architecture. To save you time and effort during initial setup, the command stacked create app my_first_app not only generates a new Flutter project but also adds Stacked to the project with startup logic and sample pages.

Adding Stacked Dependencies: You must incorporate the stacked package into your project in order to take advantage of the capabilities of the stacked architecture. ViewModels are among the crucial elements in this package that aid in efficiently managing state in your application. The get_it package, which offers a straightforward and user-friendly method of managing services, is also strongly advised for dependency injection. Consider including the hive and hive_flutter packages, which allow for lightweight local storage, if your application needs data persistence. To add these dependencies, run the command below:
flutter pub add stacked get_it hive hive_flutter

Structuring the Project for Stacked: Effective Stacked architecture implementation requires a well-structured folder system. Make specific directories for essential elements like services, viewmodels, and views.

Integrating Dependency Injection: Once your folder structure is configured, use get_it to register your services. Initialize get_it with the necessary services and create a setup_locator.dart file in the services directory. In addition to making service retrieval easier, this central registration system guarantees that the dependency inversion principle is followed.

These procedures will help you successfully incorporate Stacked into a Flutter Web project, laying the groundwork for future development that is both scalable and maintainable. Every stage, from arranging the codebase to adding dependencies, adds to a strong architecture that improves application performance and development efficiency.

Core Concepts in Stacked for Web

Building scalable and stable applications requires an understanding of the fundamental ideas behind Stacked in a Flutter Web project. The core idea of Stacked is a clear division of responsibilities, wherein the View, ViewModel, and Services collaborate to control state, business logic, and user interface. Updates between these components are guaranteed to flow smoothly because of reactive state management, which makes for a fluid development experience that is particularly useful in web contexts. In-depth explanations of these ideas' functions and interactions inside a Flutter Web project are provided in this section.

1. View and ViewModel in Flutter Web

In Stacked, the ViewModel contains the logic and state that the View needs, while the View is in charge of the application's user interface's visual representation. By keeping the UI code separate from the business logic, this division encourages a clean architecture. The View and the underlying data or services are connected by the ViewModel.

Stacked's ViewModelBuilder widget facilitates the communication between View and ViewModel in Flutter Web. By binding a particular ViewModel to a View, this widget enables the View to access the ViewModel's call methods and reactive state. With the ViewModel informing the View of updates and the View initiating actions in the ViewModel, the relationship guarantees a unidirectional data flow.

Example: Connecting the View to the ViewModel

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
class HomeViewModel extends BaseViewModel {
 String title = "Welcome to Flutter Web with Stacked!";
 void updateTitle(String newTitle) {
   title = newTitle;
   notifyListeners(); // Ensures the UI is updated reactively
 }
}
class HomeView extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return ViewModelBuilder<HomeViewModel>.reactive(
     viewModelBuilder: () => HomeViewModel(),
     builder: (context, model, child) {
       return Scaffold(
         appBar: AppBar(title: Text("Home Page")),
         body: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             Text(model.title),
             ElevatedButton(
               onPressed: () => model.updateTitle("Stacked is Awesome!"),
               child: Text("Update Title"),
             ),
           ],
         ),
       );
     },
   );
 }
}

In this example, HomeView listens to updates and reflects changes in the user interface, whereas HomeViewModel controls the state (the title). Every time the state changes, the view is recreated thanks to the notifyListeners method.

2. Services: Centralized Business Logic

Business logic is encapsulated in services in Stacked, which makes it testable and reusable. Services guarantee that ViewModels stay lightweight and concentrate only on coordinating between the View and Services by separating tasks like database interactions and API calls.

In order to make services available throughout the application, they are usually registered with a dependency injection system such as get_it. Take a service that retrieves data from an API, for instance:

import 'package:http/http.dart' as http;
class ApiService {
 final String _baseUrl = "https://jsonplaceholder.typicode.com";
 Future<List<dynamic>> fetchPosts() async {
   final response = await http.get(Uri.parse("$_baseUrl/posts"));
   if (response.statusCode == 200) {
     return List<dynamic>.from(jsonDecode(response.body));
   } else {
     throw Exception("Failed to fetch posts");
   }
 }
}

You can then register this service in a setup_locator.dart file and use it within ViewModel:

import 'package:get_it/get_it.dart';
import 'api_service.dart';
final locator = GetIt.instance;
void setupLocator() {
 locator.registerLazySingleton(() => ApiService());
}

Using the Service in a ViewModel

The centralized logic in ApiService ensures that the ViewModel is only responsible for coordinating the flow. This leaves the heavy lifting to the service:

import 'package:stacked/stacked.dart';
import 'api_service.dart';
import 'setup_locator.dart';
class PostViewModel extends BaseViewModel {
 final ApiService _apiService = locator<ApiService>();
 List<dynamic> posts = [];
 Future<void> fetchPosts() async {
   setBusy(true); // Stacked helper to indicate a loading state
   posts = await _apiService.fetchPosts();
   setBusy(false); // Remove loading state
   notifyListeners();
 }
}

3. Reactive State Management

The core of Stacked is reactive state management, which gives apps their dynamic and responsive qualities. This strategy is especially effective with Flutter Web, where the user interface frequently needs to react instantly to user input and backend changes.

The foundation of Stacked's reactivity is the notifyListeners method, which is supplied by the BaseViewModel class. Calling notifyListeners causes any widgets that are listening to a ViewModel to be rebuilt whenever the state of that ViewModel changes.

Stacked gives developers a strong basis for creating scalable Flutter Web apps by fusing Views, ViewModels, and Services with reactive state management. This architecture uses Flutter's reactivity to provide fluid, dynamic user experiences in addition to enforcing a clear separation of concerns.

Building a Simple Web App with Stacked

Using Stacked to create a basic web application shows how the framework may expedite development while maintaining clear architectural principles. We will construct a weather application that retrieves the most recent weather information for a user-specified location. This example will show you how to use Flutter widgets to define the user interface, ViewModels to implement logic, and a service to centralize business logic. To make sure the app works properly on a variety of screen sizes, we will finally combine these elements and go over how to manage responsive design.

1. Defining the UI with Flutter Widgets

The UI is the starting point for user interactions, and Flutter uses widgets to do this. The UI of a weather app consists of a display area for weather data, a button to retrieve the weather, and a search bar for inputting the location. For the best user experience, the layout should be responsive. This is an example: 

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'weather_viewmodel.dart';
class WeatherView extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return ViewModelBuilder<WeatherViewModel>.reactive(
     viewModelBuilder: () => WeatherViewModel(),
     builder: (context, model, child) {
       return Scaffold(
         appBar: AppBar(title: Text("Weather App")),
         body: Padding(
           padding: const EdgeInsets.all(16.0),
           child: Column(
             children: [
               TextField(
                 controller: model.locationController,
                 decoration: InputDecoration(
                   labelText: "Enter location",
                   border: OutlineInputBorder(),
                 ),
               ),
               const SizedBox(height: 16),
               ElevatedButton(
                 onPressed: model.fetchWeather,
                 child: Text("Get Weather"),
               ),
               const SizedBox(height: 16),
               if (model.isBusy)
                 CircularProgressIndicator()
               else if (model.weatherInfo != null)
                 Text(
                   "Temperature: ${model.weatherInfo!['temp']}°C\n"
                   "Condition: ${model.weatherInfo!['condition']}",
                   textAlign: TextAlign.center,
                 )
               else
                 Text("Enter a location to see the weather."),
             ],
           ),
         ),
       );
     },
   );
 }
}

The ElevatedButton initiates the weather fetch process, and the TextField lets the user enter a location. The View dynamically updates based on the state in the ViewModel, providing a loading indicator or weather information.

2. Implementing the ViewModel: Logic for User Actions

The ViewModel serves as the application's control center since it controls user interactions and links user interface and business logic. The ViewModel for the weather app will manage user input from the TextField, communicate with the weather service, and keep track of the app's state.

Example: WeatherViewModel

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'weather_service.dart';
import 'setup_locator.dart';
class WeatherViewModel extends BaseViewModel {
 final TextEditingController locationController = TextEditingController();
 final WeatherService _weatherService = locator<WeatherService>();
 Map<String, dynamic>? weatherInfo;
 Future<void> fetchWeather() async {
   setBusy(true);
   try {
     weatherInfo = await _weatherService.getWeather(locationController.text);
   } catch (e) {
     weatherInfo = {"temp": "N/A", "condition": "Error fetching weather"};
   } finally {
     setBusy(false);
   }
   notifyListeners();
 }
}

In order to retrieve data based on user input, the fetchWeather method communicates with the WeatherService. State modifications like as setBusy(true) guarantee the UI reflects the loading state, while notifyListeners initiates a View rebuild when data is ready.

3. Adding a Service: Centralized Logic for API Calls

Business logic and external interactions, such API requests, are encapsulated in services in Stacked. Because the weather service retrieves data from an API for this application, the ViewModel is streamlined and state management-focused.

Example: WeatherService

import 'package:http/http.dart' as http;
import 'dart:convert';
class WeatherService {
 final String _apiUrl = "https://api.open-meteo.com/v1/forecast";
 Future<Map<String, dynamic>> getWeather(String location) async {
   final response = await http.get(Uri.parse(
     "$_apiUrl?latitude=40.7128&longitude=-74.0060&current_weather=true",
   ));
   if (response.statusCode == 200) {
     final data = jsonDecode(response.body);
     return {
       "temp": data["current_weather"]["temperature"],
       "condition": data["current_weather"]["weathercode"]
     };
   } else {
     throw Exception("Failed to fetch weather data");
   }
 }
}

This service abstracts the API call. This makes it reusable and easy to test independently.

4. Connecting View, ViewModel, and Service

Wiring the View, ViewModel, and Service is the last stage. The ViewModel uses dependency injection to access the service, and the ViewModelBuilder links the View to the ViewModel. The program makes sure that a single instance of WeatherService is accessible throughout by utilizing locator.registerLazySingleton.

Registering the Service

import 'package:get_it/get_it.dart';
import 'weather_service.dart';
final locator = GetIt.instance;
void setupLocator() {
 locator.registerLazySingleton(() => WeatherService());
}

Call setupLocator in the app's main function before running the app.

1. Handling Web-Specific Features with Responsive Design

Apps may adjust to different screen sizes because of Flutter Web's flexibility, however responsive design must be used. Using packages like responsive_builder or creating layouts that adapt to MediaQuery are two ways to make Stacked responsive.

Example: Responsive Layout in Stacked

import 'package:responsive_builder/responsive_builder.dart';
@override
Widget build(BuildContext context) {
 return ScreenTypeLayout(
   mobile: WeatherMobileView(),
   tablet: WeatherTabletView(),
   desktop: WeatherDesktopView(),
 );
}

This method guarantees that the app offers the best possible experience on all platforms, including desktop, tablet, and smartphone.

By going through this procedure, we show how Stacked makes it easier to create Flutter Web apps. It enables developers them to concentrate on creating features rather than worrying about the architecture

Common Pitfalls and How to Avoid Them

Developers frequently run into issues while using Stacked in Flutter Web, which can hinder productivity and impair application performance. Building reliable and maintainable apps requires an understanding of these typical problems and the adoption of best practices to overcome them.

1. Overloading ViewModels with Logic

Putting too much business logic directly in ViewModels is a common error. The single responsibility principle is broken when ViewModels are overloaded with duties like API processing or intricate computations, even though they control state and coordinate interactions between the user interface and services. This behavior makes it more difficult to test and maintain the codebase. To get around this, assign work to specialized services or utility classes so that the ViewModel may concentrate only on UI interaction and state management.

2. Neglecting Reactive Updates

Support for reactive state management is one of Stacked's main advantages. But occasionally, developers neglect to utilize notifyListeners after changing the state of the ViewModel, which makes the user interface unresponsive. In a similar vein, if the ViewModelBuilder's reactive attribute is not used, performance may suffer from needless UI rebuilds. Maintaining seamless and effective user interface interactions requires a purposeful approach to state changes, making sure that updates only take place when required.

3. Improper Dependency Injection Setup

Misconfigured dependency injection is another frequent problem that can result in runtime issues like duplicate instances or unregistered services. This usually happens when developers instantiate services directly, avoiding the locator, or when services are not properly registered in the setupLocator function. Clean and predictable service administration is ensured by employing a consistent dependency injection pattern, such as GetIt for all service registrations.

4. Overcomplicating the Folder Structure

Although Stacked promotes a modular project structure, developers may become confused by too complex folder hierarchies, particularly when working in teams. A structure that has too many nested folders or unclear naming rules makes it more difficult to navigate and takes longer for new contributors to get up to speed. Maintain a simple yet modular folder structure that is easy to understand and follows the standards of the larger Flutter community to prevent this.

5. Inefficient API Calls and State Handling

Network efficiency and latency are important factors in web applications. Sometimes developers neglect to optimize API calls by making duplicate queries or without gracefully handling errors. For instance, the application and the API may become overloaded if data is not cached or user inputs, like search queries, are not debounced. A more robust program and improved user experience are guaranteed by utilizing strategies like result caching, input debouncing, and error boundary management.

Developers can steer clear of frequent mistakes and create apps using Stacked that are not only functional but also incredibly performant and maintainable by being aware of these pitfalls and following best practices.

Conclusion

By combining the strength of reactive state management with a distinct division of responsibilities, Stacked provides an organized and user-friendly method for developing Flutter Web apps. Stacked helps developers to build scalable, tested, and maintainable apps by separating the user interface from business logic. The development process is made simpler by the use of ViewModels for state management and services for centralized functionality, while features like dependency injection and reactive updates improve performance and efficiency. Stacked is a strong option for developers looking to produce top-notch web experiences since it offers flexibility and adaptation for web-specific requirements including responsive design and navigation.

Take your project's scale and nature into account while choosing between Stacked and alternative designs. Stacked is perfect for complicated or protracted projects since it performs exceptionally well in applications where modularity, testability, and maintainability are top concerns. Alternatives, however, can be more suitable for simpler projects or when collaborating with teams that are already accustomed to different patterns. Whichever option is selected, playing around with Stacked can help you learn more about its advantages and foster a more thorough comprehension of Flutter's potential. To promote creativity among the Flutter community, developers are urged to use Stacked in their projects, improve their workflows, and share their experiences.

Elevate Your Flutter Web Development with Stacked

Transform your Flutter web applications with Stacked—a framework designed for scalability, maintainability, and robust state management. Walturn can help you seamlessly integrate Stacked into your development process, ensuring your apps are production-ready with modular architecture and responsive designs. Let us help you unlock the full potential of Flutter Web and create unparalleled user experiences.

References

Overview | The Production Flutter Framework (2023) Filledstacks.com. Available at: https://stacked.filledstacks.com/docs/getting-started/overview (Accessed: 22 November 2024).

stacked | Flutter Package (no date) Dart packages. Available at: https://pub.dev/packages/stacked.


Other Insights

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

Learn More