Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
Documentation
Changelog
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Features
    • Security Analysis
    • Code Quality
    • Similarity Search
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Flutter

    Flutter is Googles UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase using the Dart programming language.

    Flutter Anti-Patterns Overview

    Flutter, despite its powerful cross-platform capabilities, has several common anti-patterns that can lead to performance issues, maintenance problems, and poor user experience. Here are the most important anti-patterns to avoid when writing Flutter code.

    Rebuilding Entire Widget Trees

    Copy
    // Anti-pattern: Rebuilding entire widget tree on state change
    class CounterPage extends StatefulWidget {
      @override
      _CounterPageState createState() => _CounterPageState();
    }
    
    class _CounterPageState extends State<CounterPage> {
      int _counter = 0;
      
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
      
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Counter')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('You have pushed the button this many times:'),
                Text('$_counter', style: TextStyle(fontSize: 24)),
                ExpensiveWidget(), // Rebuilds unnecessarily
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    // Better approach: Extract widgets and use const constructors
    class CounterPage extends StatefulWidget {
      @override
      _CounterPageState createState() => _CounterPageState();
    }
    
    class _CounterPageState extends State<CounterPage> {
      int _counter = 0;
      
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
      
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: const AppBar(title: Text('Counter')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('You have pushed the button this many times:'),
                CounterDisplay(count: _counter),
                const ExpensiveWidget(), // Won't rebuild
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    
    class CounterDisplay extends StatelessWidget {
      final int count;
      
      const CounterDisplay({Key? key, required this.count}) : super(key: key);
      
      @override
      Widget build(BuildContext context) {
        return Text('$count', style: const TextStyle(fontSize: 24));
      }
    }
    

    Rebuilding entire widget trees on state changes causes unnecessary work. Extract widgets, use const constructors for static widgets, and consider using packages like provider or flutter_bloc for more granular rebuilds.

    Not Using Keys Properly

    Copy
    // Anti-pattern: Not using keys for dynamic lists
    ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(items[index].title),
        );
      },
    )
    
    // Better approach: Use keys for dynamic lists
    ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ListTile(
          key: ValueKey(items[index].id), // Use unique identifier as key
          title: Text(items[index].title),
        );
      },
    )
    

    Not using keys for dynamic lists can lead to unexpected behavior when items are added, removed, or reordered. Always use keys with unique identifiers for list items.

    Mixing Business Logic with UI

    Copy
    // Anti-pattern: Business logic in widget
    class UserProfilePage extends StatefulWidget {
      @override
      _UserProfilePageState createState() => _UserProfilePageState();
    }
    
    class _UserProfilePageState extends State<UserProfilePage> {
      User? _user;
      bool _isLoading = true;
      
      @override
      void initState() {
        super.initState();
        _fetchUserData();
      }
      
      Future<void> _fetchUserData() async {
        setState(() => _isLoading = true);
        try {
          // API call directly in widget
          final response = await http.get(Uri.parse('https://api.example.com/user/123'));
          if (response.statusCode == 200) {
            setState(() {
              _user = User.fromJson(jsonDecode(response.body));
              _isLoading = false;
            });
          } else {
            throw Exception('Failed to load user');
          }
        } catch (e) {
          setState(() => _isLoading = false);
          // Error handling
        }
      }
      
      @override
      Widget build(BuildContext context) {
        // UI implementation
      }
    }
    
    // Better approach: Separate business logic
    // UserRepository class
    class UserRepository {
      Future<User> fetchUser(String id) async {
        final response = await http.get(Uri.parse('https://api.example.com/user/$id'));
        if (response.statusCode == 200) {
          return User.fromJson(jsonDecode(response.body));
        } else {
          throw Exception('Failed to load user');
        }
      }
    }
    
    // UserViewModel or BLoC
    class UserViewModel extends ChangeNotifier {
      final UserRepository _repository;
      User? user;
      bool isLoading = true;
      String? error;
      
      UserViewModel(this._repository);
      
      Future<void> fetchUser(String id) async {
        isLoading = true;
        error = null;
        notifyListeners();
        
        try {
          user = await _repository.fetchUser(id);
        } catch (e) {
          error = e.toString();
        } finally {
          isLoading = false;
          notifyListeners();
        }
      }
    }
    
    // Widget using ViewModel
    class UserProfilePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (_) => UserViewModel(UserRepository())..fetchUser('123'),
          child: Consumer<UserViewModel>(
            builder: (context, viewModel, _) {
              if (viewModel.isLoading) {
                return Center(child: CircularProgressIndicator());
              }
              if (viewModel.error != null) {
                return Center(child: Text('Error: ${viewModel.error}'));
              }
              return UserProfileView(user: viewModel.user!);
            },
          ),
        );
      }
    }
    

    Mixing business logic with UI makes code hard to test and maintain. Separate concerns using patterns like BLoC, Provider, or Riverpod.

    Inefficient List Building

    Copy
    // Anti-pattern: Building lists inefficiently
    List<Widget> buildItems() {
      List<Widget> widgets = [];
      for (var item in items) {
        widgets.add(ListTile(title: Text(item.title)));
      }
      return widgets;
    }
    
    @override
    Widget build(BuildContext context) {
      return ListView(
        children: buildItems(),
      );
    }
    
    // Better approach: Use ListView.builder
    @override
    Widget build(BuildContext context) {
      return ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(items[index].title));
        },
      );
    }
    

    Building lists by adding widgets to a list is inefficient for large lists. Use ListView.builder, GridView.builder, or other builder constructors for efficient list rendering.

    Excessive Nesting

    Copy
    // Anti-pattern: Excessive widget nesting
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Container(
            padding: EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(8),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 10,
                  offset: Offset(0, 5),
                ),
              ],
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Container(
                  padding: EdgeInsets.all(8),
                  child: Text(
                    'Welcome',
                    style: TextStyle(
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 16),
                  child: Text(
                    'This is a sample app',
                    style: TextStyle(fontSize: 16),
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    }
    
    // Better approach: Extract widgets and reduce nesting
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: WelcomeCard(),
        ),
      );
    }
    
    class WelcomeCard extends StatelessWidget {
      const WelcomeCard({Key? key}) : super(key: key);
      
      @override
      Widget build(BuildContext context) {
        return Card(
          elevation: 5,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8),
          ),
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: const [
                WelcomeHeader(),
                SizedBox(height: 16),
                WelcomeMessage(),
              ],
            ),
          ),
        );
      }
    }
    
    class WelcomeHeader extends StatelessWidget {
      const WelcomeHeader({Key? key}) : super(key: key);
      
      @override
      Widget build(BuildContext context) {
        return Text(
          'Welcome',
          style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        );
      }
    }
    
    class WelcomeMessage extends StatelessWidget {
      const WelcomeMessage({Key? key}) : super(key: key);
      
      @override
      Widget build(BuildContext context) {
        return Text(
          'This is a sample app',
          style: TextStyle(fontSize: 16),
        );
      }
    }
    

    Excessive nesting makes code hard to read and maintain. Extract widgets into smaller components and use existing Flutter widgets like Card instead of custom containers.

    Not Using const Constructors

    Copy
    // Anti-pattern: Not using const constructors
    Widget build(BuildContext context) {
      return Container(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Icon(Icons.star, size: 50, color: Colors.yellow),
            Text('Rating', style: TextStyle(fontSize: 20)),
          ],
        ),
      );
    }
    
    // Better approach: Use const constructors
    Widget build(BuildContext context) {
      return Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: const [
            Icon(Icons.star, size: 50, color: Colors.yellow),
            Text('Rating', style: TextStyle(fontSize: 20)),
          ],
        ),
      );
    }
    

    Not using const constructors for widgets with immutable properties causes unnecessary rebuilds. Use const for widgets with fixed properties to improve performance.

    Improper State Management

    Copy
    // Anti-pattern: Global variables for state
    User currentUser;
    List<Message> messages = [];
    
    class ChatPage extends StatefulWidget {
      @override
      _ChatPageState createState() => _ChatPageState();
    }
    
    class _ChatPageState extends State<ChatPage> {
      void sendMessage(String text) {
        final message = Message(text: text, sender: currentUser);
        setState(() {
          messages.add(message);
        });
      }
      
      @override
      Widget build(BuildContext context) {
        // UI implementation
      }
    }
    
    // Better approach: Proper state management
    class ChatPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (_) => ChatViewModel(),
          child: ChatView(),
        );
      }
    }
    
    class ChatViewModel extends ChangeNotifier {
      final List<Message> _messages = [];
      User? _currentUser;
      
      List<Message> get messages => List.unmodifiable(_messages);
      User? get currentUser => _currentUser;
      
      void setUser(User user) {
        _currentUser = user;
        notifyListeners();
      }
      
      void sendMessage(String text) {
        if (_currentUser == null) return;
        
        final message = Message(text: text, sender: _currentUser!);
        _messages.add(message);
        notifyListeners();
      }
    }
    
    class ChatView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final viewModel = Provider.of<ChatViewModel>(context);
        
        return Scaffold(
          appBar: AppBar(title: Text('Chat')),
          body: Column(
            children: [
              Expanded(
                child: ListView.builder(
                  itemCount: viewModel.messages.length,
                  itemBuilder: (context, index) {
                    return MessageBubble(message: viewModel.messages[index]);
                  },
                ),
              ),
              MessageInput(
                onSend: viewModel.sendMessage,
              ),
            ],
          ),
        );
      }
    }
    

    Using global variables or improper state management makes apps hard to maintain and test. Use proper state management solutions like Provider, Riverpod, or BLoC.

    Not Using Lazy Loading

    Copy
    // Anti-pattern: Loading all data at once
    class ProductsPage extends StatefulWidget {
      @override
      _ProductsPageState createState() => _ProductsPageState();
    }
    
    class _ProductsPageState extends State<ProductsPage> {
      List<Product> products = [];
      bool isLoading = true;
      
      @override
      void initState() {
        super.initState();
        _loadAllProducts();
      }
      
      Future<void> _loadAllProducts() async {
        setState(() => isLoading = true);
        try {
          // Loading all products at once
          final response = await http.get(Uri.parse('https://api.example.com/products'));
          if (response.statusCode == 200) {
            final List<dynamic> data = jsonDecode(response.body);
            setState(() {
              products = data.map((json) => Product.fromJson(json)).toList();
              isLoading = false;
            });
          }
        } catch (e) {
          setState(() => isLoading = false);
        }
      }
      
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Products')),
          body: isLoading
              ? Center(child: CircularProgressIndicator())
              : ListView.builder(
                  itemCount: products.length,
                  itemBuilder: (context, index) {
                    return ProductListItem(product: products[index]);
                  },
                ),
        );
      }
    }
    
    // Better approach: Use pagination/lazy loading
    class ProductsPage extends StatefulWidget {
      @override
      _ProductsPageState createState() => _ProductsPageState();
    }
    
    class _ProductsPageState extends State<ProductsPage> {
      final List<Product> products = [];
      bool isLoading = false;
      bool hasMore = true;
      int page = 1;
      final int pageSize = 20;
      final ScrollController _scrollController = ScrollController();
      
      @override
      void initState() {
        super.initState();
        _loadMoreProducts();
        _scrollController.addListener(_scrollListener);
      }
      
      @override
      void dispose() {
        _scrollController.removeListener(_scrollListener);
        _scrollController.dispose();
        super.dispose();
      }
      
      void _scrollListener() {
        if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.8 &&
            !isLoading && hasMore) {
          _loadMoreProducts();
        }
      }
      
      Future<void> _loadMoreProducts() async {
        if (isLoading || !hasMore) return;
        
        setState(() => isLoading = true);
        try {
          final response = await http.get(
            Uri.parse('https://api.example.com/products?page=$page&limit=$pageSize'),
          );
          
          if (response.statusCode == 200) {
            final List<dynamic> data = jsonDecode(response.body);
            final newProducts = data.map((json) => Product.fromJson(json)).toList();
            
            setState(() {
              products.addAll(newProducts);
              isLoading = false;
              page++;
              hasMore = newProducts.length == pageSize;
            });
          }
        } catch (e) {
          setState(() => isLoading = false);
        }
      }
      
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Products')),
          body: ListView.builder(
            controller: _scrollController,
            itemCount: products.length + (hasMore ? 1 : 0),
            itemBuilder: (context, index) {
              if (index == products.length) {
                return Center(child: CircularProgressIndicator());
              }
              return ProductListItem(product: products[index]);
            },
          ),
        );
      }
    }
    

    Loading all data at once can cause performance issues and poor user experience. Implement pagination or infinite scrolling for large data sets.

    Not Using Proper Image Loading

    Copy
    // Anti-pattern: Not optimizing image loading
    Image.network('https://example.com/large_image.jpg')
    
    // Better approach: Use cached_network_image
    CachedNetworkImage(
      imageUrl: 'https://example.com/large_image.jpg',
      placeholder: (context, url) => Center(child: CircularProgressIndicator()),
      errorWidget: (context, url, error) => Icon(Icons.error),
      fit: BoxFit.cover,
    )
    

    Not optimizing image loading can lead to poor performance and user experience. Use packages like cached_network_image for efficient image loading, caching, and error handling.

    Not Using Theme Properly

    Copy
    // Anti-pattern: Hardcoded styles
    Text(
      'Hello World',
      style: TextStyle(
        fontSize: 24,
        fontWeight: FontWeight.bold,
        color: Colors.blue,
      ),
    )
    
    // Better approach: Use theme
    Text(
      'Hello World',
      style: Theme.of(context).textTheme.headline5?.copyWith(
        color: Theme.of(context).primaryColor,
      ),
    )
    
    // Even better: Define a consistent theme
    MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textTheme: TextTheme(
          headline5: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
          // Other text styles
        ),
        // Other theme properties
      ),
      home: MyHomePage(),
    )
    

    Hardcoding styles makes it difficult to maintain a consistent look and feel. Use Flutter’s theming system to define and apply consistent styles throughout your app.

    Not Using Proper Error Handling

    Copy
    // Anti-pattern: Poor error handling
    Future<void> fetchData() async {
      final response = await http.get(Uri.parse('https://api.example.com/data'));
      final data = jsonDecode(response.body);
      // No error handling
    }
    
    // Better approach: Proper error handling
    Future<Result<Data>> fetchData() async {
      try {
        final response = await http.get(Uri.parse('https://api.example.com/data'));
        if (response.statusCode == 200) {
          final data = Data.fromJson(jsonDecode(response.body));
          return Result.success(data);
        } else {
          return Result.failure(
            HttpError(
              statusCode: response.statusCode,
              message: 'Failed to fetch data',
            ),
          );
        }
      } on SocketException {
        return Result.failure(ConnectionError('No internet connection'));
      } on FormatException {
        return Result.failure(ParseError('Invalid response format'));
      } catch (e) {
        return Result.failure(UnknownError(e.toString()));
      }
    }
    
    // Result class
    class Result<T> {
      final T? data;
      final AppError? error;
      
      Result.success(this.data) : error = null;
      Result.failure(this.error) : data = null;
      
      bool get isSuccess => error == null;
      bool get isFailure => error != null;
    }
    
    // Usage
    FutureBuilder<Result<Data>>(
      future: fetchData(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }
        
        if (snapshot.hasError) {
          return ErrorView(message: 'Unexpected error: ${snapshot.error}');
        }
        
        final result = snapshot.data;
        if (result == null || result.isFailure) {
          return ErrorView(
            error: result?.error ?? UnknownError('No data received'),
            onRetry: () => setState(() {}),
          );
        }
        
        return DataView(data: result.data!);
      },
    )
    

    Poor error handling leads to crashes and poor user experience. Implement proper error handling with specific error types and user-friendly error messages.

    Not Using Proper Code Organization

    Copy
    // Anti-pattern: Everything in one file
    // main.dart with models, services, widgets, etc.
    
    // Better approach: Proper code organization
    // Project structure
    // lib/
    //  ├── main.dart
    //  ├── app.dart
    //  ├── models/
    //  │    ├── user.dart
    //  │    └── product.dart
    //  ├── services/
    //  │    ├── api_service.dart
    //  │    └── auth_service.dart
    //  ├── screens/
    //  │    ├── home_screen.dart
    //  │    └── profile_screen.dart
    //  ├── widgets/
    //  │    ├── product_card.dart
    //  │    └── custom_button.dart
    //  └── utils/
    //       ├── constants.dart
    //       └── helpers.dart
    

    Poor code organization makes it difficult to maintain and scale your app. Organize your code into a clear directory structure with separate files for models, services, screens, and widgets.

    Not Using Proper Navigation

    Copy
    // Anti-pattern: Manual navigation management
    onPressed: () {
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => ProductDetailPage(product: product)),
      );
    }
    
    // Better approach: Named routes
    // In MaterialApp
    MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/products': (context) => ProductsPage(),
        '/product_detail': (context) => ProductDetailPage(),
        '/cart': (context) => CartPage(),
      },
    )
    
    // Navigation
    onPressed: () {
      Navigator.pushNamed(
        context,
        '/product_detail',
        arguments: product,
      );
    }
    
    // In ProductDetailPage
    @override
    Widget build(BuildContext context) {
      final product = ModalRoute.of(context)!.settings.arguments as Product;
      // Use product
    }
    
    // Even better: Use a navigation package like go_router or auto_route
    

    Manual navigation management can lead to inconsistent navigation behavior. Use named routes or a navigation package for more structured navigation.

    KotlinRuby
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.