Azimjon Abdurasulov

Flutter 5: Arxitektura va State Management

💙 Flutter — Mobile (iOS, Android) & Desktop (macOS, Windows)

Flutter 5: Arxitektura va State Management

Holat: 2026-yil sentabr. Katta Flutter loyihalarida eng ko'p tortishuvlarga sabab bo'ladigan mavzu — bu arxitektura va holat boshqaruvi (State Management). Ushbu darsda Flutter jamoasining rasmiy MVVM qo'llanmasi (ChangeNotifier + ListenableBuilder), Riverpod 3.4.3 va BLoC 9.1.1 taqqoslanishi, REST API bilan ishlash, ma'lumotlar bazasi (Drift vs Isar) hamda avtotestlar tahlil qilinadi.

Mundarija

  1. Rasmiy arxitektura yo'riqnomasi: MVVM va Command
  2. O'rnatilgan yechim: ChangeNotifier va ListenableBuilder
  3. Ekotizimdagi asosiy kutubxonalar: Riverpod, BLoC va Provider
  4. Taqqoslov: Flutter State vs SwiftUI @Observable
  5. Tarmoq integratsiyasi: http va qo'lda JSON parslash
  6. Mahalliy xotira: SharedPreferences va Drift
  7. Avtotestlar: Widget test va Mocktail
  8. Xulosa
  9. Manbalar

1. Rasmiy arxitektura yo'riqnomasi: MVVM va Command {#architecture}

Google Flutter jamoasi 2026-yil may oyida o'zining rasmiy arxitektura qo'llanmasini yangiladi (Flutter App Architecture Guide).

Unda tavsiya etilgan qatlamlar:

  1. UI Layer (Taqdimot qatlami): Widgetlar va foydalanuvchi bilan o'zaro aloqa.
  2. ViewModel (Boshqaruv qatlami): Biznes holatini ushlab turadi va UI uchun ma'lumotlarni tayyorlaydi.
  3. Data Layer (Ma'lumotlar qatlami): Repository va ma'lumot manbalari (REST API, Mahalliy DB).

Google'ning rasmiy Compass namunaviy ilovasida Dependency Injection (DI) uchun provider (^6.1.2), marshrutlash uchun go_router, ma'lumot modellari uchun freezed va sinovlar uchun mocktail ishlatilgan (Flutter Compass Sample).


2. O'rnatilgan yechim: ChangeNotifier va ListenableBuilder {#listenable}

Hech qanday tashqi paket o'rnatmasdan toza MVVM arxitekturasini qurish uchun Flutter'da **ChangeNotifier va ListenableBuilder** mavjud. Bu SwiftUI'dagi @Observable va avtomatik ko'rinish yangilanishiga juda yaqin:

import 'package:flutter/material.dart';

class CounterViewModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Tinglovchi widgetlarni ogohlantiradi
  }
}

class CounterScreen extends StatelessWidget {
  final CounterViewModel viewModel;
  const CounterScreen({super.key, required this.viewModel});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ListenableBuilder(
          listenable: viewModel,
          builder: (context, child) {
            return ElevatedButton(
              onPressed: viewModel.increment,
              child: Text('Qiymat: ${viewModel.count}'),
            );
          },
        ),
      ),
    );
  }
}

3. Ekotizimdagi asosiy kutubxonalar: Riverpod, BLoC va Provider {#state-libraries}

Katta jamoaviy loyihalarda odatda quyidagi uchta kutubxonadan biri tanlanadi:

1. Riverpod 3 (joriy flutter_riverpod 3.4.3)

Provider muallifi Remi Rousselet tomonidan noldan qayta yozilgan, kompilyatsiya vaqtida xavfsiz (compile-safe) va BuildContext ga bog'lanmagan zamonaviy reaktiv kutubxona:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;

  void increment() => state++;
}

final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

class RiverpodCounter extends ConsumerWidget {
  const RiverpodCounter({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return ElevatedButton(
      onPressed: () => ref.read(counterProvider.notifier).increment(),
      child: Text('Riverpod: $count'),
    );
  }
}

2. BLoC (joriy flutter_bloc 9.1.1)

Yirik korporatsiyalar, fintech va bank ilovalarida eng ommabop yondashuv. Barcha harakatlar qat'iy Hodisalar (Events) va Holatlar (States) oqimiga asoslanadi:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

sealed class CounterEvent {}
class IncrementEvent extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<IncrementEvent>((event, emit) => emit(state + 1));
  }
}

class BlocCounterView extends StatelessWidget {
  const BlocCounterView({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CounterBloc, int>(
      builder: (context, count) {
        return ElevatedButton(
          onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
          child: Text('Bloc: $count'),
        );
      },
    );
  }
}

4. Taqqoslov: Flutter State vs SwiftUI @Observable {#comparison}

XususiyatSwiftUI (iOS 17+)Flutter ChangeNotifierFlutter Riverpod 3Flutter BLoC 9
Model e'loni@Observable class Modelclass Model extends ChangeNotifierclass Model extends Notifier<T>class Bloc extends Bloc<E, S>
UI bog'lanishiAvtomatik kuzatuvListenableBuilderref.watch(provider)BlocBuilder<B, S>
Bog'liqliklar in'ektsiyasi (DI)@EnvironmentInheritedWidget / ProviderGlobal providerlarBlocProvider
Arxitektura qat'iyligiO'rtachaErkin (MVVM)Erkin / ReaktivJuda qat'iy (MVI/Unidirectional)

5. Tarmoq integratsiyasi: http va qo'lda JSON parslash {#networking}

Flutter'da rasmiy package:http paketi orqali REST so'rovlar yuboriladi. Swift'dagi Decodable ga o'xshash qulaylik uchun factory .fromJson konstruktorlari ishlatiladi:

import 'dart:convert';
import 'package:http/http.dart' as http;

class UserPost {
  final int id;
  final String title;

  const UserPost({required this.id, required this.title});

  factory UserPost.fromJson(Map<String, dynamic> json) {
    return UserPost(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Future<UserPost> fetchPost(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
  );

  if (response.statusCode == 200) {
    final jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
    return UserPost.fromJson(jsonMap);
  } else {
    throw Exception('Yuklashda xato: ${response.statusCode}');
  }
}

5.1 To'liq amaliy Mini-Ilova: Swift vs Flutter (Yonma-yon taqqoslash)

Quyida real internetdan ma'lumot yuklab, ro'yxatda aks ettiruvchi, xatoliklarni ushlovchi va Pull-to-Refresh qiluvchi to'liq modulning Swift (iOS 17+) va Flutter (Dart 3) ekvivalentlari keltirilgan:

Swift (SwiftUI + @Observable):
import SwiftUI

struct PostItem: Codable, Identifiable {
    let id: Int
    let title: String
}

@Observable
final class PostListViewModel {
    var posts: [PostItem] = []
    var isLoading = false
    var errorMessage: String?

    func fetchPosts() async {
        isLoading = true
        errorMessage = nil
        defer { isLoading = false }
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            posts = try JSONDecoder().decode([PostItem].self, from: data)
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}
Flutter (ChangeNotifier + ListenableBuilder):
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class PostModel {
  final int id;
  final String title;

  const PostModel({required this.id, required this.title});

  factory PostModel.fromJson(Map<String, dynamic> json) {
    return PostModel(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

class PostListViewModel extends ChangeNotifier {
  List<PostModel> posts = [];
  bool isLoading = false;
  String? errorMessage;

  Future<void> fetchPosts(http.Client client) async {
    isLoading = true;
    errorMessage = null;
    notifyListeners();

    try {
      final response = await client.get(
        Uri.parse('https://jsonplaceholder.typicode.com/posts'),
      );

      if (response.statusCode == 200) {
        final List<dynamic> jsonList = jsonDecode(response.body) as List<dynamic>;
        posts = jsonList
            .map((item) => PostModel.fromJson(item as Map<String, dynamic>))
            .toList();
      } else {
        errorMessage = 'Xatolik: ${response.statusCode}';
      }
    } catch (e) {
      errorMessage = 'Tarmoq xatosi: $e';
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}

class PostListWidget extends StatelessWidget {
  final PostListViewModel viewModel;
  final http.Client client;

  const PostListWidget({
    super.key,
    required this.viewModel,
    required this.client,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Postlar')),
      body: ListenableBuilder(
        listenable: viewModel,
        builder: (context, child) {
          if (viewModel.isLoading && viewModel.posts.isEmpty) {
            return const Center(child: CircularProgressIndicator());
          }
          if (viewModel.errorMessage != null && viewModel.posts.isEmpty) {
            return Center(child: Text(viewModel.errorMessage!));
          }
          return RefreshIndicator(
            onRefresh: () => viewModel.fetchPosts(client),
            child: ListView.builder(
              itemCount: viewModel.posts.length,
              itemBuilder: (context, index) {
                final post = viewModel.posts[index];
                return ListTile(
                  leading: CircleAvatar(child: Text('${post.id}')),
                  title: Text(post.title),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

6. Mahalliy xotira: SharedPreferences va Drift {#storage}

Ma'lumotlarni saqlash talabiga qarab:

  • **shared_preferences (2.5.x):** Swift'dagi UserDefaults analogi (tokenlar, til sozlamalari, flaglar uchun).
  • **drift (2.35.0):** SQLite ustidagi tiplangan qatlam (Swift'dagi GRDB yoki Core Data/SwiftData analogi). SQL so'rovlarini kompilyatsiya vaqtida tekshiradi.
  • Isar holati bo'yicha ogohlantirish: Ilgari mashhur bo'lgan Isar ma'lumotlar bazasining v4 versiyasi rasman "NOT READY FOR PRODUCTION USE" holatida qoldi. Hamjamiyat hozirda isar_community 3.3.2 vilkasidan foydalanmoqda.

7. Avtotestlar: Widget test va Mocktail {#testing}

Flutter sinovlar infratuzilmasi juda kuchli bo'lib, simulyatorsiz ham widgetlarni millisekundlarda tekshirish imkonini beradi:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Tugma bosilganda matn yangilanishi kerak', (tester) async {
    int counter = 0;
    await tester.pumpWidget(
      MaterialApp(
        home: StatefulBuilder(
          builder: (context, setState) {
            return ElevatedButton(
              onPressed: () => setState(() => counter++),
              child: Text('Soni: $counter'),
            );
          },
        ),
      ),
    );

    expect(find.text('Soni: 0'), findsOneWidget);
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
    expect(find.text('Soni: 1'), findsOneWidget);
  });
}

8. Xulosa {#xulosa}

  • Flutter uchun Google tavsiya etgan rasmiy arxitektura — MVVM bo'lib, o'rnatilgan ChangeNotifier va ListenableBuilder orqali qo'shimcha paketlarsiz ishlaydi.
  • Riverpod 3 (3.4.3) zamonaviy reaktiv ilovalar uchun eng qulay va tip xavfsiz yechimdir.
  • BLoC 9 (9.1.1) qat'iy hodisalar oqimiga ega bo'lib, yirik jamoalar va korporativ loyihalarda keng qo'llaniladi.
  • Mahalliy relyatsion bazalar uchun drift (2.35.0) eng ishonchli tanlov hisoblanadi; Isar v4 esa ishlab chiqarishga tavsiya etilmaydi.
  • Widget testlari orqali UI mantiqini simulyatorsiz o'ta yuqori tezlikda sinovdan o'tkazish mumkin.

Manbalar {#manbalar}

  1. Application Architecture Guide — Flutter Docs
  2. Compass Sample Architecture — GitHub
  3. Flutter Riverpod Documentation
  4. BLoC Library Documentation
  5. Drift Database for Dart & Flutter

Markdown manba

iOS Lessons