Tarkibga o'tish

28 β€” Platforma, paketlar va moslashuv

⬅️ Oldingi: 27 β€” Animatsiya Β· 🏠 README Β· Keyingi: 29 β€” Testing, debugging va ishlab chiqarish ➑️


Bu bobda: shu paytgacha siz Flutter'ning "ichki dunyosi"da ishladingiz β€” widgetlar, holat, navigatsiya, animatsiya. Endi ilovangizni tashqi dunyoga ulaymiz. Avval paketlar va pub.dev ekotizimi bilan tanishamiz: flutter pub add qanday ishlaydi, pubspec.yamldagi versiya cheklovlari (^), va yaxshi paketni qanday tanlash kerak. So'ng qurilma imkoniyatlarini paketlar orqali ishlatamiz β€” image_picker (kamera/galereya), geolocator (joylashuv), url_launcher, permission_handler (ruxsatlar). Keyin platform channel g'oyasi bilan tanishamiz β€” paket bo'lmaganda native (Kotlin/Swift) kodga murojaat qilish. Undan keyin moslashuvchi (responsive) va adaptiv dizayn β€” telefon, planshet, desktop va orientatsiyaga moslashish. Oxirida xalqarolashtirish (i18n) β€” ilovani ikki tilda qilish β€” va qulaylik (accessibility) β€” ilovani hamma uchun ishlatsa bo'ladigan qilish. Bu bob keng, lekin har bir mavzu amaliy va loyihangizga darhol kerak bo'ladi.


Nega paketlar? G'ildirakni qaytadan ixtiro qilmang

Tasavvur qiling: ilovangizga foydalanuvchi suratini galereyadan tanlash imkoniyati kerak. Buni noldan yozish β€” kamera ruxsatlari, Android va iOS uchun alohida native kod, fayl yo'llarini boshqarish β€” bir necha kunlik mehnat. Ammo kimdir buni allaqachon yozib, sinab, minglab ilovalarda ishlatgan va bepul ulashgan. Siz uni bir buyruq bilan loyihangizga qo'shasiz.

Bu paket (package) β€” boshqalar yozgan, qayta ishlatsa bo'ladigan kod to'plami. Flutter'ning paket do'koni β€” pub.dev β€” minglab paketni saqlaydi. Bu xuddi JavaScript'dagi npm yoki Python'dagi PyPI'ga o'xshaydi: nima kerak bo'lsa, ehtimol kimdir uni yozib qo'ygan.

πŸ’‘ Nega o'zingiz yozmaysiz? Yaxshi paket yuzlab odam tomonidan sinalgan, turli qurilmalarda tekshirilgan va doimiy yangilanib turadi. Sizning bir kunda yozgan kodingiz hech qachon shu darajaga yetolmaydi. Dasturlashda "tayyor yechimni ishlatish" β€” dangasalik emas, donolik.

pubspec.yaml β€” ilovangizning pasporti

Har bir Flutter loyihasida pubspec.yaml nomli fayl bor. Bu β€” loyihangizning pasporti: nomi, versiyasi, bog'liqliklari (paketlari), shrift va rasm (asset)lari shu yerda e'lon qilinadi. Eng muhim qismi β€” dependencies:

name: mening_ilovam
description: Birinchi Flutter ilovam.
version: 1.0.0

environment:
  sdk: ^3.10.0

dependencies:
  flutter:
    sdk: flutter
  http: ^1.5.0          # tarmoq paketi (21-bobdan tanish)

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0  # faqat ishlab chiqishda kerak

Ikki turdagi bog'liqlik bor:

  • dependencies β€” ilovangiz ishlash uchun kerak bo'lgan paketlar (http, geolocator). Ular tayyor ilovaga ham qo'shiladi.
  • dev_dependencies β€” faqat ishlab chiqish paytida kerak bo'lganlar (test paketlari, linter, kod generatorlari). Ular tayyor ilovaga kirmaydi, shuning uchun uning hajmini oshirmaydi.

⚠️ pubspec.yaml β€” YAML formatida. Bu yerda bo'shliqlar (indentatsiya) muhim: tab ishlatmang, har bosqichda 2 ta bo'shliq qo'ying. Bitta noto'g'ri bo'shliq butun faylni buzadi.

Paketni qo'shish: flutter pub add

Paketni qo'lda pubspec.yamlga yozish o'rniga, eng oson yo'l β€” terminalda buyruq berish. Loyiha papkasida:

flutter pub add geolocator

Bu buyruq uch ishni qiladi: paketning eng so'nggi mos versiyasini topadi, uni pubspec.yamlning dependenciesiga yozadi, va yuklab oladi. Faylga shunday qator qo'shiladi:

dependencies:
  geolocator: ^14.0.0

dev_dependenciesga qo'shish uchun --dev bayrog'ini qo'shing:

flutter pub add --dev build_runner

Agar siz pubspec.yamlni qo'lda tahrirlasangiz (masalan, hamkasbingizning loyihasini ochib), paketlarni yuklash uchun:

flutter pub get

flutter pub add aslida flutter pub getni o'zi chaqiradi, shuning uchun ko'pincha add yetarli. pub get β€” loyihani birinchi marta klonlaganingizda yoki faylni qo'lda o'zgartirganingizda kerak bo'ladi.

pub.dev β†’ flutter pub add β†’ pubspec.yaml β†’ import: paketni qo'shishning to'rt qadami

Versiya cheklovlari: ^ belgisi nimani anglatadi?

geolocator: ^14.0.0 dagi ^ (karet) belgisi muhim. U "14.0.0 va undan yuqori, lekin 15.0.0 dan past" degani. Ya'ni:

  • 14.0.1, 14.3.0, 14.9.9 β€” mumkin (kichik yangilanishlar, xatolar tuzatilishi).
  • 15.0.0 β€” mumkin emas (katta versiya, buzuvchi o'zgarishlar bo'lishi mumkin).

Bu semantik versiya (semantic versioning) qoidasi: KATTA.KICHIK.TUZATISH. Katta raqam o'zgarsa β€” eski kod buzilishi mumkin; kichik raqam β€” yangi imkoniyat (eski kod ishlaydi); oxirgisi β€” xato tuzatish. ^ belgisi sizni avtomatik xato tuzatishlar bilan ta'minlaydi, lekin kutilmagan buzilishlardan himoya qiladi.

πŸ’‘ flutter pub upgrade paketlarni cheklov ichida eng yangiga ko'taradi. flutter pub outdated esa qaysi paketlar eskirganini ko'rsatadi β€” vaqti-vaqti bilan tekshirib turing.

pub.dev sahifasini qanday o'qish kerak

pub.devda paket sahifasiga kirganingizda quyidagilarga e'tibor bering β€” bu sizga paket ishonchliligini baholashga yordam beradi:

  • Platformalar β€” paket sahifasida qaysi platformalar (Android, iOS, Web, Windows, macOS, Linux) qo'llab-quvvatlanishi yozadi. Sizning ilovangiz veb-da ham ishlashi kerak bo'lsa, paket Web'ni qo'llab-quvvatlashini tekshiring.
  • Likes (yoqtirishlar) va popularity (mashhurlik) β€” ko'p odam ishlatadigan paket odatda yaxshiroq sinalgan.
  • Pub Points β€” pub.dev'ning avtomatik baho-ochkosi (hujjat, null-safety, kod sifati bo'yicha).
  • Oxirgi yangilanish sanasi β€” paket faol qo'llab-quvvatlanayotganini ko'rsatadi. 2-3 yil yangilanmagan paketdan ehtiyot bo'ling.
  • Null-safety β€” zamonaviy paketlar null-safety'ni qo'llab-quvvatlaydi (6-bobni eslang). Deyarli barcha hozirgi paketlar buni qiladi.

⚠️ Paket tanlashda "eng birinchi qidiruv natijasi"ni ko'r-ko'rona olmang. Platformalar, mashhurlik va oxirgi yangilanishni tekshiring. Notanish, kam ishlatiladigan paket loyihangizga keyinchalik bosh og'rig'i bo'lishi mumkin.

Qurilma imkoniyatlaridan foydalanish

Endi paketlarni amalda ishlatamiz. Telefonning haqiqiy imkoniyatlari β€” kamera, joylashuv, telefon qo'ng'irog'i β€” uchun maxsus paketlar bor. Mana eng ko'p ishlatiladiganlari:

Paket Nima qiladi
image_picker Galereyadan rasm tanlash yoki kamera bilan suratga olish
geolocator Qurilmaning GPS joylashuvini olish
url_launcher Havola, telefon raqami yoki email'ni ochish
permission_handler Ish vaqtidagi (runtime) ruxsatlarni so'rash va tekshirish
connectivity_plus Internet aloqasi bor-yo'qligini bilish (Wi-Fi/mobil/yo'q)
camera Ilova ichida to'liq kamera oqimini boshqarish
shared_preferences Kichik sozlamalarni qurilmada saqlash
package_info_plus / device_info_plus Ilova versiyasi / qurilma haqida ma'lumot

Misol: galereyadan rasm tanlash (image_picker)

Avval paketni qo'shamiz:

flutter pub add image_picker

So'ng foydalanish β€” galereyadan rasm tanlash:

import 'package:image_picker/image_picker.dart';

final picker = ImagePicker();

Future<void> rasmTanla() async {
  final XFile? rasm = await picker.pickImage(source: ImageSource.gallery);
  if (rasm == null) return; // foydalanuvchi bekor qildi
  print('Tanlangan fayl: ${rasm.path}');
  // rasm.path orqali uni Image.file(...) bilan ko'rsatishingiz mumkin
}

ImageSource.gallery o'rniga ImageSource.camera bersangiz, kamera ochiladi. XFile? β€” tanlangan fayl (null bo'lsa, foydalanuvchi bekor qilgan; har doim tekshiring).

Misol: joriy joylashuvni olish (geolocator)

import 'package:geolocator/geolocator.dart';

Future<Position?> joylashuvniOl() async {
  // 1) Joylashuv xizmati (GPS) yoqilganmi?
  final yoqilgan = await Geolocator.isLocationServiceEnabled();
  if (!yoqilgan) return null;

  // 2) Ruxsatni tekshirish va so'rash
  var ruxsat = await Geolocator.checkPermission();
  if (ruxsat == LocationPermission.denied) {
    ruxsat = await Geolocator.requestPermission();
    if (ruxsat == LocationPermission.denied) return null;
  }
  if (ruxsat == LocationPermission.deniedForever) return null;

  // 3) Joylashuvni olish
  return await Geolocator.getCurrentPosition();
}

E'tibor bering: joylashuvni so'rashdan oldin uch narsani tekshiramiz β€” GPS yoqilganmi, ruxsat bormi, agar yo'q bo'lsa so'raymiz. Bu naqsh (pattern) deyarli barcha qurilma imkoniyatlarida takrorlanadi.

Misol: tashqi havola, telefon, email (url_launcher)

import 'package:url_launcher/url_launcher.dart';

// Veb-sahifa ochish
await launchUrl(Uri.parse('https://ioqil.uz'));

// Telefon raqamini terish
await launchUrl(Uri.parse('tel:+998901234567'));

// Email yozish
await launchUrl(Uri.parse('mailto:salom@misol.uz'));

Uri.parse(...) matnni manzilga aylantiradi; tel:, mailto:, https: β€” turli sxemalar. Telefon qurilmasi shu sxemaga mos ilovani ochadi.

⚠️ Eng muhim qoida: ruxsatlarni e'lon qiling

Kamera, joylashuv, mikrofon kabi imkoniyatlardan foydalanish uchun ikki narsa kerak:

1) Platforma fayllarida e'lon qilish. Android'da bu android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>

iOS'da bu ios/Runner/Info.plist β€” bu yerda foydalanuvchiga ko'rinadigan sabab ham yoziladi:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Yaqin atrofdagi do'konlarni ko'rsatish uchun joylashuvingiz kerak.</string>
<key>NSCameraUsageDescription</key>
<string>Profil rasmingizni olish uchun kameradan foydalanamiz.</string>

2) Ish vaqtida (runtime) so'rash. Zamonaviy telefonlarda ilova o'rnatilganida emas, balki kerak bo'lganda ruxsat so'raladi. Yuqoridagi geolocator misolida buni ko'rdik. Murakkabroq holatlar uchun permission_handler paketi yagona, qulay interfeys beradi:

import 'package:permission_handler/permission_handler.dart';

final holat = await Permission.camera.request();
if (holat.isGranted) {
  // ruxsat berildi β€” kamerani ishlat
} else if (holat.isPermanentlyDenied) {
  // foydalanuvchi "boshqa so'rama" dedi β€” sozlamalarni ochishni taklif qil
  await openAppSettings();
}

πŸ’‘ Esda tuting: ruxsatni faqat platforma faylida e'lon qilsangiz, ish vaqtida so'ramaysiz β€” ilova ishlamaydi (yoki qulaydi). Ikkalasini ham qiling. Va har doim foydalanuvchi rad etishi mumkinligini hisobga oling β€” kodingiz buni boshqarishi shart.

Platform channel β€” paket bo'lmaganda

99% holatda kerakli imkoniyat uchun tayyor paket topiladi. Lekin ba'zan juda maxsus narsa kerak bo'ladi (masalan, kompaniyangizning xususiy qurilmasi bilan ishlash), va paket yo'q. Shunda siz native kodga (Android uchun Kotlin, iOS uchun Swift) to'g'ridan-to'g'ri murojaat qilishingiz mumkin β€” buning ko'prigi platform channel deyiladi.

G'oyasi oddiy: Dart tomoni va native tomoni xabarlar orqali gaplashadi. Dart "menga batareya darajasini ber" deb xabar yuboradi, native kod javob qaytaradi:

import 'package:flutter/services.dart';

// Ikkala tomon ham bir xil kanal nomini bilishi kerak
const battery = MethodChannel('app/battery');

Future<int> batareyaDarajasi() async {
  // 'getLevel' β€” native tomonda biz yozadigan metod nomi
  final int daraja = await battery.invokeMethod('getLevel');
  return daraja;
}

Native tomonda (masalan Android β€” Kotlin) 'getLevel' so'rovini qabul qilib, batareya darajasini o'qib qaytaradigan kod yoziladi. Bu kitob doirasidan tashqarida, lekin g'oyani tushunish muhim: kanal nomi ('app/battery') va metod nomi ('getLevel') ikki tomonda bir xil bo'lishi kerak β€” aks holda xabar manzilini topmaydi.

Platform channel: Dart invokeMethod xabar yuboradi, native (Kotlin/Swift) qaytaradi

πŸ’‘ Boshlovchi uchun amaliy maslahat: platform channel β€” ilg'or mavzu. Deyarli har doim avval pub.dev'da tayyor paket qidiring. Channel'ni faqat haqiqatan paket yo'q bo'lganda va siz native kod yoza oladigan paytda ishlatasiz. Uni bu yerda eslab qo'yganimizning sababi β€” Flutter'ning native qurilma bilan qanday "gaplashishi"ni tushunishingiz uchun.

Moslashuvchi (responsive) va adaptiv dizayn

Hozirgacha ilovamiz, ehtimol, faqat telefon ekranida yaxshi ko'ringan. Lekin Flutter bir kod bilan telefon, planshet, buklanadigan (foldable), veb va desktopda ishlaydi. Bularning ekran o'lchami juda har xil. Ilovangiz katta ekranda ham yaxshi ko'rinishi uchun unga moslashuvchi bo'lishni o'rgatish kerak.

Ikki yaqin tushunchani ajratamiz:

  • Responsive (moslashuvchi) β€” o'lcham o'zgarganda joylashuv o'zgaradi. Masalan, telefonda bir ustun, planshetda ikki ustun.
  • Adaptive (adaptiv) β€” platforma yoki kontekstga mos boshqa komponent ishlatiladi. Masalan, telefonda pastki menyu (NavigationBar), planshetda yon menyu (NavigationRail).

Amalda ikkalasini birga ishlatasiz. Asboblarimiz:

MediaQuery β€” ekran haqida ma'lumot

MediaQuery β€” joriy ekran haqidagi ma'lumotlar manbai: o'lcham, orientatsiya, xavfsiz hududlar (notch), matn kattaligi:

final media = MediaQuery.of(context);
final kenglik = media.size.width;       // ekran kengligi (piksellarda)
final balandlik = media.size.height;
final orientatsiya = media.orientation;  // portrait yoki landscape
final tepaPadding = media.padding.top;   // status bar / notch balandligi

LayoutBuilder β€” ota cheklovini o'qish

MediaQuery butun ekran o'lchamini beradi. LayoutBuilder esa ota-widget bergan joyni (constraints) o'qiydi β€” bu ko'pincha aniqroq, chunki sizning widget'ingiz ekranning faqat bir qismini egallashi mumkin. Buni 13-bobda ko'rgansiz:

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const TelefonKorinishi(); // tor joy
    } else {
      return const KengKorinishi();    // keng joy
    }
  },
)

Breakpoint'lar (chegaralar)

Ekran kengligi bo'yicha "telefon / planshet / desktop"ni ajratish uchun chegara qiymatlar (breakpoints) ishlatiladi. Material dizayn tavsiyasiga yaqin amaliy chegaralar:

enum Qurilma { telefon, planshet, desktop }

Qurilma qurilmaTuri(double kenglik) {
  if (kenglik < 600) return Qurilma.telefon;
  if (kenglik < 1200) return Qurilma.planshet;
  return Qurilma.desktop;
}

Bir ilova uch kenglikda: telefon NavigationBar, planshet va desktop NavigationRail

Adaptiv navigatsiya: NavigationBar ↔ NavigationRail

Endi amalda quramiz: tor ekranda pastki menyu (NavigationBar), keng ekranda yon menyu (NavigationRail). Bu β€” adaptiv dizaynning klassik misoli:

class AdaptivKorinish extends StatefulWidget {
  const AdaptivKorinish({super.key});

  @override
  State<AdaptivKorinish> createState() => _AdaptivKorinishState();
}

class _AdaptivKorinishState extends State<AdaptivKorinish> {
  int _tanlangan = 0;

  // Uchta bo'lim uchun oddiy sahifalar
  final _sahifalar = const [
    Center(child: Text('Uy')),
    Center(child: Text('Qidiruv')),
    Center(child: Text('Profil')),
  ];

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final keng = constraints.maxWidth >= 600;

        if (keng) {
          // KENG: yon NavigationRail + tarkib yonma-yon
          return Scaffold(
            body: Row(
              children: [
                NavigationRail(
                  selectedIndex: _tanlangan,
                  onDestinationSelected: (i) =>
                      setState(() => _tanlangan = i),
                  labelType: NavigationRailLabelType.all,
                  destinations: const [
                    NavigationRailDestination(
                        icon: Icon(Icons.home), label: Text('Uy')),
                    NavigationRailDestination(
                        icon: Icon(Icons.search), label: Text('Qidiruv')),
                    NavigationRailDestination(
                        icon: Icon(Icons.person), label: Text('Profil')),
                  ],
                ),
                const VerticalDivider(width: 1),
                Expanded(child: _sahifalar[_tanlangan]),
              ],
            ),
          );
        } else {
          // TOR: pastki NavigationBar
          return Scaffold(
            body: _sahifalar[_tanlangan],
            bottomNavigationBar: NavigationBar(
              selectedIndex: _tanlangan,
              onDestinationSelected: (i) =>
                  setState(() => _tanlangan = i),
              destinations: const [
                NavigationDestination(icon: Icon(Icons.home), label: 'Uy'),
                NavigationDestination(
                    icon: Icon(Icons.search), label: 'Qidiruv'),
                NavigationDestination(
                    icon: Icon(Icons.person), label: 'Profil'),
              ],
            ),
          );
        }
      },
    );
  }
}

Diqqat qiling: tanlangan indeks (_tanlangan) bitta β€” telefonda ham, planshetda ham bir xil holat ishlatiladi. Faqat ko'rinish o'zgaradi. Bu β€” adaptiv dizaynning go'zalligi: bir mantiq, ikki tashqi ko'rinish.

πŸ’‘ Sinash: desktop yoki veb-da ilovani ishga tushirib, oynani sichqoncha bilan torayting-kengaytiring. 600px chegarasidan o'tganda menyu pastdan yonga (yoki teskari) o'tib turishini ko'rasiz. OrientationBuilder esa shunga o'xshash, lekin orientatsiya (portret/landshaft) o'zgarganda ishlaydi.

Xalqarolashtirish (i18n) β€” ilovani ko'p tilda qilish

Ilovangizni o'zbek va ingliz tillarida ko'rsatmoqchimisiz? Buni i18n (internationalization β€” "xalqarolashtirish", 18 ta harf orasida bo'lgani uchun shunday qisqartiriladi) deyiladi. Matnlarni kodga to'g'ridan-to'g'ri yozish o'rniga, ularni alohida til fayllariga ajratasiz, Flutter esa qurilma tiliga qarab to'g'risini tanlaydi.

1-qadam: paketlarni qo'shish

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

Va pubspec.yamlga generatsiyani yoqamiz:

flutter:
  generate: true

2-qadam: ARB fayllari (tarjimalar)

Loyihada lib/l10n/ papkasini yarating va har bir til uchun bitta ARB fayl (.arb β€” JSON'ga o'xshash format). Ingliz tili β€” app_en.arb:

{
  "greeting": "Hello!",
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "placeholders": { "count": { "type": "int" } }
  }
}

O'zbek tili β€” app_uz.arb:

{
  "greeting": "Salom!",
  "itemCount": "{count, plural, =0{Element yo'q} =1{1 ta element} other{{count} ta element}}"
}

itemCount β€” ko'plik (plural) misoli: count qiymatiga qarab to'g'ri shakl tanlanadi. {count} β€” o'rin tutuvchi (placeholder), unga haqiqiy son qo'yiladi.

3-qadam: kod generatsiya qilish

flutter gen-l10n

Bu buyruq ARB fayllaridan AppLocalizations klassini avtomatik yaratadi. (Agar generate: true qo'ygan bo'lsangiz, flutter run paytida ham avtomatik ishlaydi.)

4-qadam: MaterialAppga ulash

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales, // [en, uz]
  // locale: const Locale('uz'), // majburan bitta tilni qo'yish (ixtiyoriy)
  home: const BoshSahifa(),
)

5-qadam: matnlardan foydalanish

Endi kodda matnni AppLocalizationsdan olasiz:

@override
Widget build(BuildContext context) {
  final l10n = AppLocalizations.of(context)!;
  return Column(
    children: [
      Text(l10n.greeting),         // "Salom!" yoki "Hello!"
      Text(l10n.itemCount(5)),     // "5 ta element" / "5 items"
    ],
  );
}

AppLocalizations.of(context)! β€” joriy tilga mos tarjimalarni beradi. Qurilma tili o'zbek bo'lsa β€” o'zbekcha, ingliz bo'lsa β€” inglizcha matn chiqadi. Sehr shu yerda: matn endi bir joyda (ARB fayl), kodda esa faqat kalit (greeting).

πŸ’‘ RTL (o'ngdan-chapga) qo'llab-quvvatlash. Arab yoki ibroniy kabi o'ngdan-chapga yoziladigan tillar uchun Flutter Directionalityni avtomatik boshqaradi: Row, padding'dagi start/end to'g'ri tomonga aylanadi. Shuning uchun left/right o'rniga start/end (masalan EdgeInsetsDirectional) ishlatish β€” yaxshi odat.

Qulaylik (accessibility, a11y) β€” ilovani hamma uchun

Qulaylik (accessibility, qisqacha a11y) β€” ilovangizni ko'rish, eshitish yoki harakat qiyinchiligi bor odamlar ham ishlata olishini ta'minlash. Bu nafaqat insoniy mas'uliyat, balki App Store va Google Play talablariga ham kiradi. Yaxshi xabar: Flutter buning ko'p qismini avtomatik qiladi, sizdan esa bir nechta odatlar talab etiladi.

Ekran o'quvchilarga (screen reader) yordam: Semantics

Ko'zi ojiz foydalanuvchilar ekran o'quvchi (Android'da TalkBack, iOS'da VoiceOver) bilan ishlaydi β€” u ekrandagi narsalarni ovoz bilan o'qib beradi. Matnli widgetlarni Flutter o'zi tushuntiradi, lekin rasm yoki ikonka tugmasi nima ekanini bilmaydi. Shuning uchun ularga yorliq (label) berasiz:

// Rasm uchun semanticLabel:
Image.asset('rasmlar/logo.png', semanticLabel: 'Kompaniya logotipi')

// Ikonka uchun:
Icon(Icons.favorite, semanticLabel: 'Sevimlilarga qo\'shilgan')

// Murakkabroq holat uchun Semantics widgeti:
Semantics(
  label: 'Yangi xabar yozish',
  button: true,
  child: GestureDetector(
    onTap: _yozish,
    child: const Icon(Icons.edit),
  ),
)

Aksincha, bezak uchun (hech qanday ma'no bermaydigan) rasmni ekran o'quvchidan yashirish kerak bo'lsa β€” ExcludeSemantics:

ExcludeSemantics(child: Image.asset('rasmlar/bezak.png'))

Matn kattaligini hurmat qiling: textScaler

Ko'zi xira foydalanuvchilar telefon sozlamalarida matnni kattalashtiradi. Agar siz balandlikni qattiq belgilab qo'ysangiz (SizedBox(height: 40) ichida katta matn), matn sig'maydi va kesiladi. Yechim: qattiq balandlik bermang, matnga o'sishiga ruxsat bering. Joriy masshtabni MediaQuerydan o'qishingiz mumkin:

final masshtab = MediaQuery.textScalerOf(context);

⚠️ Eng keng tarqalgan a11y xatosi β€” matn o'lchamini kattalashtirgan foydalanuvchida buziladigan dizayn. Sinash uchun: telefon sozlamalarida shrift o'lchamini eng kattaga qo'ying va ilovangizni tekshiring β€” hamma narsa ko'rinib turibdimi?

Yetarli kontrast va katta bosish maydoni

  • Rang kontrasti. Matn va orqa fon orasida yetarli farq bo'lishi kerak (och kulrang fonda och kulrang matn β€” yomon). WCAG tavsiyasi: oddiy matn uchun kamida 4.5:1 kontrast nisbati.
  • Bosish maydoni (tap target). Tugmalar va bosiladigan elementlar kamida 48Γ—48 piksel bo'lishi kerak β€” aks holda barmoq bilan aniq bosish qiyin. Material widgetlari (IconButton, ElevatedButton) buni odatda o'zi ta'minlaydi.

Birlashtirib: qulay rasm-tugma

Mana, hammasini birlashtirgan kichik misol β€” qulay (accessible) rasm-tugma:

Semantics(
  label: 'Profil rasmini o\'zgartirish',
  button: true,
  child: InkWell(
    onTap: _rasmTanla,
    child: Padding(
      padding: const EdgeInsets.all(12), // 48x48 ga yetkazadi
      child: const Icon(Icons.camera_alt, size: 24),
    ),
  ),
)

Bunda: Semantics ekran o'quvchiga "Profil rasmini o'zgartirish, tugma" deydi; Padding bosish maydonini 48Γ—48 ga yetkazadi; InkWell bosilganda to'lqin effekti beradi. Bir nechta qator β€” katta foyda.

πŸ’‘ Nega bu muhim? Dunyoda milliardlab odam u yoki bu darajada qulaylik imkoniyatlariga muhtoj. Ilovangiz ularni ham qamrab olsa β€” siz auditoriyangizni kengaytirasiz va to'g'ri ish qilasiz. Bundan tashqari, App Store/Play Store ilovani tekshirganda a11y'ni hisobga oladi.

Birgalikda: keng, ko'p tilli va qulay ilova

Endi o'rgangan uch g'oyani bitta MaterialAppda birlashtiramiz β€” adaptiv navigatsiya (AdaptivKorinish, yuqorida), ikki tilli i18n va qulay tugmalar:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Moslashuvchi ilova',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
      ),
      // i18n: ikki til (en + uz)
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      // Adaptiv navigatsiya: telefon NavigationBar ↔ planshet NavigationRail
      home: const AdaptivKorinish(),
    );
  }
}

Bu ilova endi: katta ekranlarda yon menyu, kichik ekranlarda pastki menyu ko'rsatadi; qurilma tiliga qarab o'zbekcha yoki inglizcha matn chiqaradi; ekran o'quvchilar va katta matn bilan ham to'g'ri ishlaydi. Bularning hammasini bir kod bazasi bilan β€” Flutter'ning kuchi shunda.

Keyingi qadam

Bu bobda ilovangizni tashqi dunyoga uladingiz: paketlar va pub.dev, qurilma imkoniyatlari (kamera, joylashuv, ruxsatlar), platform channel g'oyasi, moslashuvchi va adaptiv dizayn, xalqarolashtirish va qulaylik. Endi ilovangiz turli qurilma, til va foydalanuvchiga moslasha oladi.

Lekin yaxshi ilova β€” nafaqat ishlaydigan, balki ishonchli ilova. Keyingi 29-bobda testing, debugging va ishlab chiqarish mavzusiga o'tamiz: kodingizni avtomatik test bilan qanday himoyalash, xatolarni topish (debugging), unumdorlikni (performance) yaxshilash va ilovani do'konga chiqarishga tayyorlash. Shu paytda bu bobdagi paketlar va adaptiv kodlarni ham test bilan mustahkamlaymiz.


Mashqlar

Oson

  1. dependencies va dev_dependencies orasidagi farq nima? flutter_lints qaysi biriga kiradi va nega?
  2. http: ^1.5.0 cheklovi qaysi versiyalarni ruxsat etadi va qaysilarini etmaydi? 1.9.0 va 2.0.0 β€” qaysi biri mumkin?
  3. Galereyadan rasm tanlash uchun qaysi paketni qo'shasiz va terminal buyrug'i qanday bo'ladi? pickImage qaytaradigan qiymat null bo'lsa, bu nimani anglatadi?
  4. Responsive va adaptive dizayn orasidagi farqni o'z so'zlaringiz bilan, bittadan misol bilan tushuntiring.

O'rta

  1. Bir o'quvchi geolocator bilan joylashuvni olmoqchi, AndroidManifest.xmlga ruxsatni qo'shgan, lekin ilova hech narsa qaytarmayapti. U nimani unutgan bo'lishi mumkin? Joylashuvni olishdan oldin tekshirilishi kerak bo'lgan uch narsani sanang.
  2. LayoutBuilder bilan shunday widget yozing: agar ota kengligi 600 dan kichik bo'lsa Column, aks holda Row qaytarsin (ichida ixtiyoriy ikkita matn bo'lsin). MediaQuery o'rniga LayoutBuilder ishlatishning afzalligi nima?
  3. i18n sozlashda app_en.arb va app_uz.arb fayllarining vazifasi nima? flutter gen-l10n buyrug'i nima qiladi va kodda matnni qanday olasiz?

Qiyin

  1. Quyidagi tugma a11y nuqtai nazaridan yomon: GestureDetector(onTap: ..., child: Icon(Icons.delete, size: 18)). Uni qulayroq qiling β€” kamida ikkita muammoni toping va tuzating.
  2. Platform channel qachon kerak bo'ladi va u qanday ishlaydi (xabar oqimini tushuntiring)? Nega boshlovchiga avval pub.dev'da paket qidirish tavsiya etiladi? Kanal nomi ikki tomonda bir xil bo'lmasa nima bo'ladi?
  3. Bir ilova telefonda yaxshi ishlaydi, lekin planshetda matn juda kichik va menyu pastda noqulay. Adaptiv qilish uchun siz qaysi uch narsani o'zgartirasiz? Har biri uchun qaysi widget yoki yondashuvni ishlatishingizni ayting.
Yechimlar

1. dependencies β€” ilova ishlash uchun kerak bo'lgan paketlar; ular tayyor ilovaga ham qo'shiladi. dev_dependencies β€” faqat ishlab chiqish paytida kerak bo'lganlar (testlar, linterlar, generatorlar); ular tayyor ilovaga kirmaydi. flutter_lints β€” dev_dependenciesga kiradi, chunki u faqat kod yozish paytida tahlil/maslahat beradi; tayyor ilova ishlashiga aloqasi yo'q, shuning uchun uni ilova hajmiga qo'shish keraksiz.

2. ^1.5.0 β€” "1.5.0 dan yuqori, lekin 2.0.0 dan past" degani. 1.9.0 β€” mumkin (hali 2.0.0 dan past, kichik versiya). 2.0.0 β€” mumkin emas (katta versiya, buzuvchi o'zgarish bo'lishi mumkin, shuning uchun ^ uni o'tkazmaydi).

3. Paket β€” image_picker, buyruq:

flutter pub add image_picker
pickImage(...) null qaytarsa β€” bu foydalanuvchi bekor qilgani (rasm tanlamay chiqib ketgani) degani. Shuning uchun har doim if (rasm == null) return; bilan tekshirish kerak.

4. Responsive β€” o'lcham o'zgarganda joylashuv moslashadi (masalan, telefonda bir ustun, planshetda ikki ustun β€” bir xil widgetlar, boshqa joylashuv). Adaptive β€” kontekstga mos boshqa komponent ishlatiladi (masalan, telefonda NavigationBar, planshetda NavigationRail β€” boshqa widget). Qisqasi: responsive = "qayta joylashtirish", adaptive = "boshqa komponent tanlash".

5. U ish vaqtida (runtime) ruxsat so'rashni unutgan bo'lishi mumkin β€” AndroidManifest.xmlda e'lon qilish yetarli emas, zamonaviy Android'da ruxsatni kod orqali ham so'rash kerak. Joylashuvdan oldin tekshirilishi kerak bo'lgan uch narsa: 1. Joylashuv xizmati (GPS) yoqilganmi β€” Geolocator.isLocationServiceEnabled(). 2. Ruxsat holatini tekshirish β€” Geolocator.checkPermission(). 3. Agar denied bo'lsa, ruxsat so'rash β€” Geolocator.requestPermission() (va deniedForever holatini boshqarish).

6.

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const Column(
        children: [Text('Birinchi'), Text('Ikkinchi')],
      );
    } else {
      return const Row(
        children: [Text('Birinchi'), Text('Ikkinchi')],
      );
    }
  },
)
Afzalligi: MediaQuery butun ekran o'lchamini beradi, LayoutBuilder esa ota-widget bergan haqiqiy joyni (constraints) beradi. Agar widget'ingiz ekranning faqat bir qismida (masalan, yon panel ichida) bo'lsa, LayoutBuilder aniqroq qaror beradi.

7. app_en.arb va app_uz.arb β€” har bir til uchun tarjima fayllari (kalit β†’ matn juftliklari). flutter gen-l10n ulardan AppLocalizations klassini avtomatik generatsiya qiladi. Kodda matnni shunday olasiz:

final l10n = AppLocalizations.of(context)!;
Text(l10n.greeting); // joriy tilga mos matn
Qurilma tiliga qarab to'g'ri til avtomatik tanlanadi.

8. Ikki (yoki undan ko'p) muammo: (a) ekran o'quvchi tugmaning nima ekanini bilmaydi β€” semanticLabel/Semantics yo'q; (b) bosish maydoni juda kichik (size: 18, hech qanday padding yo'q) β€” 48Γ—48 ga yetmaydi. Tuzatilgan versiya:

Semantics(
  label: 'O\'chirish',
  button: true,
  child: InkWell(
    onTap: _ochir,
    child: const Padding(
      padding: EdgeInsets.all(14), // bosish maydonini kattalashtiradi
      child: Icon(Icons.delete, size: 24),
    ),
  ),
)
(Yoki sodda yo'l: IconButton(onPressed: _ochir, icon: Icon(Icons.delete), tooltip: 'O\'chirish') β€” u 48Γ—48 va semantikani o'zi ta'minlaydi.)

9. Platform channel β€” kerakli imkoniyat uchun tayyor paket yo'q bo'lganda, native (Kotlin/Swift) kodga to'g'ridan-to'g'ri murojaat qilish uchun kerak. Xabar oqimi: Dart MethodChannel('nom').invokeMethod('metod') bilan so'rov xabari yuboradi β†’ ko'prik orqali native tomonga o'tadi β†’ native kod uni bajarib, javob xabarini qaytaradi β†’ natija Dart'da await orqali olinadi. Boshlovchiga avval paket qidirish tavsiya etiladi, chunki channel ilg'or mavzu (native kod yozishni talab qiladi) va 99% holatda tayyor, sinalgan paket allaqachon mavjud. Kanal nomi ikki tomonda bir xil bo'lmasa, xabar manzilini topmaydi β€” native kod chaqirilmaydi (xato yoki javobsizlik bo'ladi).

10. Uch o'zgarish: 1. Navigatsiyani adaptiv qilish β€” keng ekranda pastki NavigationBar o'rniga yon NavigationRail ko'rsatish. Buni LayoutBuilder + kenglik chegarasi (masalan >= 600) bilan tanlash. 2. Joylashuvni moslashtirish β€” LayoutBuilder/MediaQuery bilan kenglikni o'lchab, keng ekranda tarkibni ikki ustun qilib yoki maksimal kenglik berib (juda cho'zilmasligi uchun) joylashtirish. 3. Matnni moslashtirish β€” qattiq kichik shrift o'lchamlarini ishlatmaslik; Themedagi matn uslublariga tayanish va MediaQuery.textScalerOf(context)ni hurmat qilish, balandlikni qattiq belgilamaslik β€” shunda katta ekranda ham, kattalashtirilgan matnda ham to'g'ri ko'rinadi.


⬅️ Oldingi: 27 β€” Animatsiya Β· 🏠 README Β· Keyingi: 29 β€” Testing, debugging va ishlab chiqarish ➑️