Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • 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, 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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.