আপনার প্রথম Flutter অ্যাপ

১. ভূমিকা

Flutter হল Google-এর UI টুলকিট যা একটিই কোডবেস থেকে মোবাইল, ওয়েব ও ডেস্কটপের জন্য অ্যাপ্লিকেশন তৈরি করতে সাহায্য করে। এই কোডল্যাবে আপনি নিম্নলিখিত Flutter অ্যাপ্লিকেশন তৈরি করবেন:

অ্যাপ্লিকেশনটি "newstay", "lightstream", "mainbrake" বা "graypine"-এর মতো আকর্ষণীয় নাম তৈরি করে। ব্যবহারকারী পরবর্তী নাম জিজ্ঞাসা করতে, বর্তমান নামটি পছন্দ করতে এবং আলাদা পৃষ্ঠায় পছন্দের তালিকা পর্যালোচনা করতে পারবেন। অ্যাপটি বিভিন্ন স্ক্রিন সাইজের সাথে মানানসই।

আপনি কী শিখবেন

  • Flutter কীভাবে কাজ করে তার প্রাথমিক তথ্য
  • Flutter-এ লেআউট তৈরি করা
  • ব্যবহারকারীর ইন্টার‍্যাকশন (যেমন বোতাম প্রেস করা) অ্যাপের আচরণের সাথে কানেক্ট করা
  • আপনার Flutter কোড সাজিয়ে রাখা
  • আপনার অ্যাপকে রেসপন্সিভ করা (বিভিন্ন স্ক্রিনের জন্য)
  • আপনার অ্যাপের লুক ও ফিল যাতে একই রকম থাকে তা নিশ্চিত করা

আপনি একটি বেসিক স্ক্যাফোল্ড দিয়ে শুরু করবেন যাতে আপনি সরাসরি আকর্ষণীয় অংশে যেতে পারেন।

e9c6b402cd8003fd.png

আর এখানে ফিলিপ আপনাকে পুরো কোডল্যাবটি দেখাবে!

ল্যাব শুরু করতে 'পরবর্তী' বিকল্পে ক্লিক করুন।

২. আপনার Flutter এনভায়রনমেন্ট সেট-আপ করুন

এডিটর

এই কোডল্যাব যতটা সম্ভব সহজ করার জন্য, আমরা ধরে নিচ্ছি যে আপনি ডেভেলপমেন্ট এনভায়রনমেন্ট হিসেবে Visual Studio Code (VS Code) ব্যবহার করবেন। এটি ফ্রি এবং সব প্রধান প্ল্যাটফর্মে কাজ করে।

অবশ্যই, আপনি যেকোনও এডিটর ব্যবহার করতে পারেন: Android Studio, অন্যান্য IntelliJ IDE, Emacs, Vim বা Notepad++। এগুলি সবই Flutter-এর সাথে কাজ করে।

আমরা এই কোডল্যাবের জন্য VS Code ব্যবহার করার সাজেশন দিই কারণ নির্দেশাবলী ডিফল্ট হিসেবে VS Code-নির্দিষ্ট শর্টকাট ব্যবহার করে। "X করার জন্য আপনার এডিটরে উপযুক্ত অ্যাকশন নিন" এই ধরনের কিছু বলার পরিবর্তে "এখানে ক্লিক করুন" বা "এই কী প্রেস করুন" এই ধরনের কিছু বলা সহজ।

228c71510a8e868.png

ডেভেলপমেন্ট টার্গেট বেছে নিন

Flutter হল একটি মাল্টি-প্ল্যাটফর্ম টুলকিট। আপনার অ্যাপ নিম্নলিখিত যেকোনও অপারেটিং সিস্টেমে রান করতে পারে:

  • iOS
  • Android
  • Windows
  • macOS
  • Linux
  • ওয়েব

তবে, একটি অপারেটিং সিস্টেম বেছে নেওয়া সাধারণ প্র্যাক্টিস যার জন্য আপনি প্রাথমিকভাবে ডেভেলপ করবেন। এটি হল আপনার "ডেভেলপমেন্ট টার্গেট"—ডেভেলপমেন্টের সময় আপনার অ্যাপ যে অপারেটিং সিস্টেমে চলে।

16695777c07f18e5.png

যেমন, ধরুন আপনি Flutter অ্যাপ ডেভেলপ করার জন্য Windows ল্যাপটপ ব্যবহার করছেন। আপনি ডেভেলপমেন্ট টার্গেট হিসেবে Android বেছে নিলে, সাধারণত USB কেবেল দিয়ে Windows ল্যাপটপের সাথে Android ডিভাইস অ্যাটাচ করেন এবং আপনার ডেভেলপমেন্ট-ইন-অ্যাপ সেই অ্যাটাচ করা Android ডিভাইসে চলে। তবে, আপনি ডেভেলপমেন্ট টার্গেট হিসেবে Windows বেছে নিতে পারেন, যার অর্থ হল আপনার অ্যাপ-ইন-ডেভেলপমেন্ট আপনার এডিটরের পাশাপাশি Windows অ্যাপ হিসেবে রান করে।

আপনার ডেভেলপমেন্ট টার্গেট হিসেবে ওয়েব বেছে নেওয়ার ইচ্ছা হতে পারে। এই বিকল্পের নেতিবাচক দিক হল, আপনি Flutter-এর সবচেয়ে উপযোগী ডেভেলপমেন্ট ফিচারগুলির একটি হারাবেন: স্টেটফুল হট রিলোড। Flutter ওয়েব অ্যাপ্লিকেশন হট-রিলোড করতে পারে না।

এখনই আপনার পছন্দ বেছে নিন। মনে রাখবেন: আপনি পরে যেকোনও সময় অন্য অপারেটিং সিস্টেমে আপনার অ্যাপ চালাতে পারবেন। ডেভেলপমেন্টের লক্ষ্য স্পষ্ট থাকলে পরবর্তী ধাপগুলি আরও মসৃণ হয়।

Flutter ইনস্টল করুন

Flutter SDK কীভাবে ইনস্টল করতে হয় সেই সংক্রান্ত সবচেয়ে আপ-টু-ডেট নির্দেশাবলী সবসময় docs.flutter.dev লিঙ্কে পাবেন।

Flutter ওয়েবসাইটের নির্দেশাবলীতে শুধু SDK ইনস্টল করার পদ্ধতিই নয়, ডেভেলপমেন্ট টার্গেট-সম্পর্কিত টুল এবং এডিটর প্লাগ-ইন সম্পর্কেও তথ্য দেওয়া আছে। মনে রাখবেন, এই কোডল্যাবের জন্য আপনাকে শুধু নিম্নলিখিতগুলি ইনস্টল করতে হবে:

  1. Flutter SDK
  2. Flutter প্লাগ-ইন সহ Visual Studio Code
  3. আপনার বেছে নেওয়া ডেভেলপমেন্ট টার্গেটের জন্য প্রয়োজনীয় সফ্টওয়্যার (যেমন: Windows-এর জন্য Visual Studio বা macOS-এর জন্য Xcode)

পরবর্তী বিভাগে, আপনি নিজের প্রথম Flutter প্রোজেক্ট তৈরি করবেন।

এখনও পর্যন্ত আপনার সমস্যা হলে, সমস্যার সমাধান করার জন্য আপনি এইসব প্রশ্ন ও উত্তরের (StackOverflow থেকে) কিছু সহায়ক হিসেবে পেতে পারেন।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

৩. প্রোজেক্ট তৈরি করুন

আপনার প্রথম Flutter প্রোজেক্ট তৈরি করুন

Visual Studio Code চালু করে কমান্ড প্যালেট খুলুন (F1 বা Ctrl+Shift+P বা Shift+Cmd+P দিয়ে)। "flutter new" টাইপ করা শুরু করুন। Flutter: নতুন প্রোজেক্ট কমান্ড বেছে নিন।

এরপর, অ্যাপ্লিকেশন এবং তারপর আপনার প্রোজেক্ট তৈরি করার জন্য একটি ফোল্ডার বেছে নিন। এটি আপনার হোম ডিরেক্টরি বা C:\src\-এর মতো কিছু হতে পারে।

সবশেষে, আপনার প্রোজেক্টের নাম দিন। namer_app বা my_awesome_namer-এর মতো কিছু।

260a7d97f9678005.png

Flutter এখন আপনার প্রোজেক্ট ফোল্ডার তৈরি করে এবং VS Code এটি খোলে।

আপনি এখন অ্যাপের বেসিক স্ক্যাফোল্ড সহ ৩টি ফাইলের কন্টেন্ট ওভাররাইট করবেন।

প্রাথমিক অ্যাপ কপি ও পেস্ট করুন

VS কোডের বাঁদিকের প্যানে, এক্সপ্লোরার বেছে নেওয়া হয়েছে কিনা তা নিশ্চিত করুন এবং pubspec.yaml ফাইলটি খুলুন।

e2a5bab0be07f4f7.png

এই ফাইলের কন্টেন্ট নিম্নলিখিত কন্টেন্ট দিয়ে পরিবর্তন করুন:

pubspec.yaml

name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0

environment:
  sdk: ^3.9.0

dependencies:
  flutter:
    sdk: flutter
  english_words: ^4.0.0
  provider: ^6.1.5

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0

flutter:
  uses-material-design: true

pubspec.yaml ফাইল আপনার অ্যাপ সম্পর্কে প্রাথমিক তথ্য নির্দিষ্ট করে, যেমন এর বর্তমান ভার্সন, এর ডিপেন্ডেন্সি এবং অ্যাসেট যার সাথে এটি শিপ করা হবে।

এরপর, প্রোজেক্টে অন্য একটি কনফিগারেশন ফাইল খুলুন, analysis_options.yaml।

a781f218093be8e0.png

এর কন্টেন্ট নিম্নলিখিত কন্টেন্ট দিয়ে পরিবর্তন করুন:

analysis_options.yaml

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    avoid_print: false
    prefer_const_constructors_in_immutables: false
    prefer_const_constructors: false
    prefer_const_literals_to_create_immutables: false
    prefer_final_fields: false
    unnecessary_breaks: true
    use_key_in_widget_constructors: false

আপনার কোড বিশ্লেষণ করার সময় Flutter কতটা কঠোর হবে তা এই ফাইল নির্ধারণ করে। আপনি প্রথমবার Flutter ব্যবহার করছেন বলে, আপনি অ্যানালাইজারকে বলছেন যাতে এটি সহজভাবে নেওয়া হয়। আপনি পরে যেকোনও সময় এটি টিউন করতে পারবেন। আসলে, আপনি যখন কোনও প্রোডাকশন অ্যাপ প্রকাশ করার কাছাকাছি পৌঁছে যাবেন, তখন আপনি অবশ্যই অ্যানালাইজারকে এর চেয়ে আরও কঠোর করতে চাইবেন।

সবশেষে, lib/ ডিরেক্টরির অধীনে main.dart ফাইলটি খুলুন।

e54c671c9bb4d23d.png

এই ফাইলের কন্টেন্ট নিম্নলিখিত কন্টেন্ট দিয়ে পরিবর্তন করুন:

lib/main.dart

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [Text('A random idea:'), Text(appState.current.asLowerCase)],
      ),
    );
  }
}

এই ৫০ লাইনের কোডই হল এখনও পর্যন্ত অ্যাপের সম্পূর্ণতা।

পরবর্তী বিভাগে, ডিবাগ মোডে অ্যাপ্লিকেশনটি রান করুন এবং ডেভেলপ করা শুরু করুন।

৪. একটি বোতাম যোগ করুন

এই ধাপটি নতুন শব্দ জোড়া তৈরি করতে একটি পরবর্তী বোতাম যোগ করে।

অ্যাপ লঞ্চ করুন

প্রথমে, lib/main.dart খুলুন এবং আপনার টার্গেট ডিভাইস বেছে নিয়েছেন কিনা তা ভাল করে দেখে নিন। VS কোডের নিচে ডানদিকের কোণে, আপনি একটি বোতাম দেখতে পাবেন যা বর্তমান টার্গেট ডিভাইস দেখায়। এটি পরিবর্তন করতে ক্লিক করুন।

lib/main.dart খোলা থাকাকালীন, VS Code-এর উইন্ডোর উপরের ডানদিকের কোণে "প্লে" b0a5d0200af5985d.png বোতামটি খুঁজে বের করে ক্লিক করুন।

প্রায় এক মিনিট পরে, আপনার অ্যাপ ডিবাগ মোডে চালু হয়। এখনও পর্যন্ত খুব বেশি কিছু নেই:

f96e7dfb0937d7f4.png

প্রথম হট রিলোড

lib/main.dart-এর নিচে, প্রথম Text অবজেক্টের স্ট্রিংয়ে কিছু যোগ করুন এবং ফাইলটি সেভ করুন (Ctrl+S বা Cmd+S দিয়ে)। যেমন:

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),  // ← Example change.
          Text(appState.current.asLowerCase),
        ],
      ),
    );

// ...

লক্ষ্য করুন, কীভাবে অ্যাপটি সাথে সাথে পরিবর্তিত হয় কিন্তু র‍্যান্ডম শব্দটি একই থাকে। এটি হল Flutter-এর বিখ্যাত স্টেটফুল হট রিলোড যা কাজ করছে। আপনি সোর্স ফাইলে পরিবর্তন সেভ করলে হট রিলোড ট্রিগার হয়।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

বোতাম যোগ করা হচ্ছে

এরপর, Column-এর নিচে, দ্বিতীয় Text ইনস্ট্যান্সের ঠিক নিচে একটি বোতাম যোগ করুন।

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(appState.current.asLowerCase),

          // ↓ Add this.
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),

        ],
      ),
    );

// ...

আপনি পরিবর্তন সেভ করলে, অ্যাপ আবার আপডেট হয়: একটি বোতাম দেখা যায় এবং আপনি সেটিতে ক্লিক করলে, VS কোডে ডিবাগ কনসোল একটি বোতাম প্রেস করা হয়েছে! মেসেজ দেখায়।

৫ মিনিটে Flutter ক্র্যাশ কোর্স

ডিবাগ কনসোল দেখার মতো মজার হলেও, আপনি বোতামটি দিয়ে আরও অর্থপূর্ণ কিছু করতে চান। তবে, সেখানে যাওয়ার আগে, lib/main.dart-এ কোডটি আরও ভালোভাবে দেখুন, যাতে এটি কীভাবে কাজ করে তা বুঝতে পারেন।

lib/main.dart

// ...

void main() {
  runApp(MyApp());
}

// ...

ফাইলের একেবারে উপরে, আপনি main() ফাংশনটি দেখতে পাবেন। বর্তমানে এটি শুধু Flutter-কে MyApp-এ উল্লেখ করা অ্যাপ রান করতে বলে।

lib/main.dart

// ...

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

// ...

MyApp ক্লাস StatelessWidget-কে এক্সটেন্ড করে। উইজেট হল এমন এলিমেন্ট যা দিয়ে আপনি প্রতিটি Flutter অ্যাপ তৈরি করেন। আপনি দেখতেই পাচ্ছেন যে, এমনকি অ্যাপ নিজেই একটি উইজেট।

MyApp-এ থাকা কোডটি পুরো অ্যাপ সেট-আপ করে। এটি অ্যাপ-ব্যাপী স্টেট তৈরি করে (এই বিষয়ে পরে আরও জানবেন), অ্যাপের নাম দেয়, ভিজ্যুয়াল থিম নির্ধারণ করে এবং "হোম" উইজেট সেট করে—আপনার অ্যাপের স্টার্টিং পয়েন্ট।

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

// ...

এরপর, MyAppState ক্লাস অ্যাপের...মানে...স্টেট নির্ধারণ করে। Flutter-এ এটি আপনার প্রথম প্রচেষ্টা, তাই এই কোডল্যাবটি সহজ এবং ফোকাসড হবে। Flutter-এ অ্যাপের স্ট্যাটাস ম্যানেজ করার অনেক শক্তিশালী উপায় আছে। সবচেয়ে সহজে ব্যাখ্যা করা যায় এমন একটি পদ্ধতি হল ChangeNotifier, এই অ্যাপে এই পদ্ধতি ব্যবহার করা হয়।

  • অ্যাপ কাজ করার জন্য কোন ডেটা প্রয়োজন তা MyAppState নির্ধারণ করে। বর্তমানে, এতে শুধু বর্তমান র‍্যান্ডম শব্দ জোড়ার সাথে একটি ভেরিয়েবল রয়েছে। আপনি পরে এটিতে যোগ করবেন।
  • স্টেট ক্লাস ChangeNotifier-কে এক্সটেন্ড করে, যার অর্থ হল এটি নিজের পরিবর্তন সম্পর্কে অন্যদের জানাতে পারে। যেমন, বর্তমান শব্দ জোড়া পরিবর্তন হলে, অ্যাপের কিছু উইজেটকে তা জানতে হবে।
  • স্টেট তৈরি করা হয় এবং ChangeNotifierProvider (MyApp-এ উপরে কোড দেখুন) ব্যবহার করে পুরো অ্যাপে প্রদান করা হয়। এটি অ্যাপের যেকোনও উইজেটকে স্টেট হোল্ড করার অনুমতি দেয়।

d9b6ecac5494a6ff.png

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {           // ← 1
    var appState = context.watch<MyAppState>();  // ← 2

    return Scaffold(                             // ← 3
      body: Column(                              // ← 4
        children: [
          Text('A random AWESOME idea:'),        // ← 5
          Text(appState.current.asLowerCase),    // ← 6
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),
        ],                                       // ← 7
      ),
    );
  }
}

// ...

সবশেষে, MyHomePage আছে, যে উইজেট আপনি আগেই পরিবর্তন করেছেন। নিচে নম্বর দেওয়া প্রতিটি লাইন উপরের কোডের লাইন-নম্বর কমেন্টের সাথে ম্যাপ করা আছে:

  1. প্রতিটি উইজেট একটি build() পদ্ধতিকে সংজ্ঞায়িত করে যা উইজেটের পরিস্থিতি পরিবর্তন হওয়ার সাথে সাথে অটোমেটিক কল করা হয় যাতে উইজেটটি সর্বদা আপ-টু-ডেট থাকে।
  2. MyHomePage, watch পদ্ধতি ব্যবহার করে অ্যাপের বর্তমান অবস্থার পরিবর্তন ট্র্যাক করে।
  3. প্রতিটি build মেথডকে অবশ্যই একটি উইজেট বা (আরও সাধারণত) উইজেটের একটি নেস্টেড ট্রি রিটার্ন করতে হবে। এই ক্ষেত্রে, টপ-লেভেল উইজেট হল Scaffold। আপনি এই কোডল্যাবে Scaffold-এর সাথে কাজ করবেন না, তবে এটি একটি সহায়ক উইজেট এবং বাস্তব-জগতের বেশিরভাগ Flutter অ্যাপে পাওয়া যায়।
  4. Flutter-এ সবচেয়ে বেসিক লেআউট উইজেটগুলির মধ্যে অন্যতম হল Column। এটি যেকোনও সংখ্যক চাইল্ড এলিমেন্ট নিয়ে উপর থেকে নিচে একটি কলামে সাজায়। ডিফল্ট হিসেবে, কলামটি তার চিলড্রেনকে ভিজ্যুয়ালি সবচেয়ে উপরে রাখে। আপনি শীঘ্রই এটি পরিবর্তন করবেন যাতে কলামটি কেন্দ্রে থাকে।
  5. আপনি প্রথম ধাপে এই Text উইজেট পরিবর্তন করেছেন।
  6. এই দ্বিতীয় Text উইজেট appState নেয় এবং সেই ক্লাসের একমাত্র মেম্বার current (যা হল একটি WordPair) অ্যাক্সেস করে। WordPair বেশ কিছু সহায়ক গেটার প্রদান করে, যেমন asPascalCase বা asSnakeCase। এখানে, আমরা asLowerCase ব্যবহার করি, তবে আপনি বিকল্পগুলির মধ্যে একটি পছন্দ করলে এখন এটি পরিবর্তন করতে পারবেন।
  7. লক্ষ্য করুন যে Flutter কোড কীভাবে ট্রেলিং কমা ব্যাপকভাবে ব্যবহার করে। এই নির্দিষ্ট কমাটি এখানে থাকার দরকার নেই, কারণ children হল এই নির্দিষ্ট Column প্যারামিটার তালিকার শেষ (এবং একমাত্র) মেম্বার। তবে, ট্রেলিং কমা ব্যবহার করা সাধারণত ভাল আইডিয়া: এর ফলে আরও মেম্বার যোগ করা সহজ হয় এবং Dart-এর অটো-ফরম্যাটারকে সেখানে নতুন লাইন যোগ করার ইঙ্গিতও দেয়। আরও তথ্যের জন্য, কোড ফর্ম্যাটিং দেখুন।

এরপর, আপনি বোতামটি স্টেটের সাথে কানেক্ট করবেন।

আপনার প্রথম আচরণ

স্ক্রল করে MyAppState-এ যান এবং getNext পদ্ধতি যোগ করুন।

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  // ↓ Add this.
  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }
}

// ...

নতুন getNext() পদ্ধতি current-কে নতুন র‍্যান্ডম WordPair-এর সাথে আবার অ্যাসাইন করে। এটি notifyListeners() (ChangeNotifier-এর একটি পদ্ধতি) কল করে যা নিশ্চিত করে যে MyAppState দেখছেন এমন প্রত্যেককে বিজ্ঞপ্তি পাঠানো হয়েছে।

এখন শুধু বোতামের কলব্যাক থেকে getNext পদ্ধতি কল করতে হবে।

lib/main.dart

// ...

    ElevatedButton(
      onPressed: () {
        appState.getNext();  // ← This instead of print().
      },
      child: Text('Next'),
    ),

// ...

সেভ করুন এবং এখনই অ্যাপ ব্যবহার করে দেখুন। আপনি যতবার পরবর্তী বোতাম প্রেস করবেন, ততবারই এটি একটি নতুন র‍্যান্ডম শব্দ জোড়া তৈরি করবে।

পরের বিভাগে, আপনি ইউজার ইন্টারফেস আরও সুন্দর করে তুলবেন।

৫. অ্যাপটি আরও সুন্দর করে তুলুন

বর্তমানে অ্যাপটি এইরকম দেখতে।

3dd8a9d8653bdc56.png

ভাল নয়। অ্যাপের মূল অংশ—র‍্যান্ডমভাবে তৈরি করা শব্দের জোড়া—আরও স্পষ্টভাবে দেখা যেতে হবে। আসলে, আমাদের ব্যবহারকারীরা এই অ্যাপ ব্যবহার করেন তার প্রধান কারণই হল এটি! এছাড়াও, অ্যাপের কন্টেন্ট অদ্ভুতভাবে অফ-সেন্টার এবং পুরো অ্যাপটি বিরক্তিকরভাবে সাদা-কালো।

এই বিভাগটি অ্যাপের ডিজাইনে কাজ করে এইসব সমস্যার সমাধান করে। এই বিভাগের চূড়ান্ত লক্ষ্য নিম্নলিখিত বিষয়ের মতো কিছু:

2bbee054d81a3127.png

উইজেট এক্সট্র্যাক্ট করা

বর্তমান শব্দজোড়া দেখানোর জন্য দায়ী লাইনটি এখন এইরকম দেখতে: Text(appState.current.asLowerCase)। এটিকে আরও জটিল কিছুতে পরিবর্তন করতে, এই লাইনটি একটি আলাদা উইজেটে এক্সট্র্যাক্ট করা একটি ভাল ধারণা। Flutter-এ জটিলতা ম্যানেজ করার একটি গুরুত্বপূর্ণ উপায় হল আপনার UI-এর আলাদা আলাদা লজিক্যাল অংশের জন্য আলাদা আলাদা উইজেট থাকা।

উইজেট এক্সট্র্যাক্ট করার জন্য Flutter একটি রিফ্যাক্টরিং হেল্পার প্রদান করে, তবে আপনি এটি ব্যবহার করার আগে, নিশ্চিত করুন যে এক্সট্র্যাক্ট করা লাইনটি শুধুমাত্র তার প্রয়োজনীয় জিনিস অ্যাক্সেস করছে। বর্তমানে, লাইনটি appState অ্যাক্সেস করে, কিন্তু আসলে শুধুমাত্র বর্তমান শব্দ জোড়া কী তা জানতে হবে।

সেই কারণে, MyHomePage উইজেটটি এইভাবে আবার লিখুন:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;                 // ← Add this.

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(pair.asLowerCase),                // ← Change to this.
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

দারুণ। Text উইজেট আর সম্পূর্ণ appState-কে রেফার করে না।

এখন, রিফ্যাক্টর মেনু কল-আপ করুন। VS Code-এ, আপনি দুটি উপায়ের মধ্যে যেকোনও একটিতে এটি করতে পারেন:

  1. আপনি যে কোডটি রিফ্যাক্টর করতে চান (এই ক্ষেত্রে Text) সেটির উপর ডানদিকের বোতামে ক্লিক করুন এবং ড্রপ-ডাউন মেনু থেকে রিফ্যাক্টর... বিকল্প বেছে নিন,

অথবা

  1. আপনি যে কোড রিফ্যাক্টর করতে চান (এই ক্ষেত্রে, Text), আপনার কার্সর সেখানে নিয়ে যান এবং Ctrl+. (Win/Linux) বা Cmd+. (Mac) প্রেস করুন।

রিফ্যাক্টর মেনু থেকে, উইজেট এক্সট্র্যাক্ট করুন বিকল্প বেছে নিন। BigCard-এর মতো কোনও নাম দিন এবং Enter-এ ক্লিক করুন।

এটি বর্তমান ফাইলের শেষে অটোমেটিক একটি নতুন ক্লাস, BigCard, তৈরি করে। ক্লাসটি দেখতে নিম্নলিখিত উদাহরণের মতো হবে:

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair});

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Text(pair.asLowerCase);
  }
}

// ...

লক্ষ্য করুন, এই রিফ্যাক্টরিংয়ের পরেও অ্যাপটি কীভাবে কাজ করে চলেছে।

কার্ড যোগ করুন

এখন এই নতুন উইজেটকে UI-এর একটি বোল্ড পিস হিসেবে তৈরি করার সময় এসেছে যা আমরা এই বিভাগের শুরুতে কল্পনা করেছিলাম।

BigCard ক্লাস এবং এর মধ্যে build() পদ্ধতি খুঁজুন। আগের মতোই, Text উইজেটে রিফ্যাক্টর মেনু কল করুন। তবে, এইবার আপনি উইজেট এক্সট্র্যাক্ট করতে পারবেন না।

পরিবর্তে, প্যাডিং সহ র‍্যাপ করুন বিকল্প বেছে নিন। এটি Text উইজেটের চারপাশে Padding নামে একটি নতুন পেরেন্ট উইজেট তৈরি করে। সেভ করার পরে, আপনি দেখতে পাবেন যে র‍্যান্ডম শব্দে ইতিমধ্যেই আরও বেশি ব্রিদিং রুম আছে।

8.0-এর ডিফল্ট ভ্যালু থেকে প্যাডিং বাড়ান। যেমন, আরও বড় প্যাডিংয়ের জন্য 20-এর মতো কিছু ব্যবহার করুন।

এরপর, এক লেভেল উপরে যান। Padding উইজেটে আপনার কার্সর রাখুন, রিফ্যাক্টর মেনু উপরে টানুন এবং উইজেট দিয়ে র‍্যাপ করুন... বিকল্প বেছে নিন।

এটি আপনাকে পেরেন্ট উইজেট নির্দিষ্ট করার অনুমতি দেয়। "কার্ড" টাইপ করে Enter প্রেস করুন।

এটি Padding উইজেট এবং সেই কারণে Text-কেও Card উইজেটের সাথে র‍্যাপ করে।

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair});

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }
}

// ...

অ্যাপটি এখন দেখতে এরকম হবে:

6031adbc0a11e16b.png

থিম ও স্টাইল

কার্ডটি আরও আকর্ষণীয় করে তুলতে, এটি আরও গাঢ় রঙে পেইন্ট করুন। আর যেহেতু সবসময় একই রঙের স্কিম ব্যবহার করা ভালো, তাই রঙ বেছে নিতে অ্যাপের Theme ব্যবহার করুন।

BigCard-এর build() পদ্ধতিতে নিম্নলিখিত পরিবর্তন করুন।

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);       // ← Add this.

    return Card(
      color: theme.colorScheme.primary,    // ← And also this.
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }

// ...

এই দুটি নতুন লাইন অনেক কাজ করে:

  • প্রথমে, কোডটি Theme.of(context)-এর সাথে অ্যাপের বর্তমান থিমের জন্য অনুরোধ করে।
  • তারপর, কোডটি কার্ডের রঙকে থিমের colorScheme প্রপার্টির মতো একই হিসেবে সংজ্ঞায়িত করে। রঙের স্কিমে অনেক রঙ আছে এবং primary হল সবচেয়ে বিশিষ্ট, অ্যাপের রঙ নির্ধারণ করে।

কার্ডটি এখন অ্যাপের প্রাথমিক রঙে আঁকা হয়েছে:

a136f7682c204ea1.png

আপনি এই রঙ এবং পুরো অ্যাপের রঙের স্কিম পরিবর্তন করতে পারবেন। এর জন্য MyApp-এ স্ক্রল করে যান এবং সেখানে ColorScheme-এর জন্য সিড কালার পরিবর্তন করুন।

রঙ কীভাবে মসৃণভাবে অ্যানিমেট করে তা লক্ষ্য করুন। একে ইমপ্লিসিট অ্যানিমেশন বলা হয়। অনেক Flutter উইজেট ভ্যালুর মধ্যে মসৃণভাবে ইন্টারপোলেট করবে যাতে UI শুধু স্টেটগুলির মধ্যে "জাম্প" না করে।

কার্ডের নিচে থাকা এলিভেটেড বোতামের রঙও পরিবর্তন হয়। হার্ড-কোডিং ভ্যালুর পরিবর্তে অ্যাপ-জুড়ে Theme ব্যবহার করার সুবিধা এটিই।

TextTheme

কার্ডে এখনও সমস্যা আছে: টেক্সট খুব ছোট এবং এর রঙ সহজে পড়া যায় না। এটি ঠিক করতে, BigCard-এর build() পদ্ধতিতে নিম্নলিখিত পরিবর্তনগুলি করুন।

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    // ↓ Add this.
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),
        // ↓ Change this line.
        child: Text(pair.asLowerCase, style: style),
      ),
    );
  }

// ...

এই পরিবর্তনের কারণ:

  • theme.textTheme ব্যবহার করে, আপনি অ্যাপের ফন্ট থিম অ্যাক্সেস করতে পারবেন। এই ক্লাসে bodyMedium (মাঝারি সাইজের স্ট্যান্ডার্ড টেক্সটের জন্য), caption (ছবির ক্যাপশনের জন্য) বা headlineLarge (বড় হেডলাইনের জন্য)-এর মতো মেম্বার অন্তর্ভুক্ত থাকে।
  • displayMedium প্রপার্টি হল ডিসপ্লে টেক্সটের জন্য বড় স্টাইল। এখানে ডিসপ্লে শব্দটি টাইপোগ্রাফিক অর্থে ব্যবহার করা হয়েছে, যেমন ডিসপ্লে টাইপফেস। displayMedium-এর ডকুমেন্টেশনে বলা হয়েছে যে "ডিসপ্লে স্টাইল ছোট, গুরুত্বপূর্ণ টেক্সটের জন্য সংরক্ষিত"—ঠিক আমাদের ব্যবহারের ক্ষেত্রে যেমন।
  • তত্ত্বগতভাবে থিমের displayMedium প্রপার্টি null হতে পারে। Dart, যে প্রোগ্রামিং ল্যাঙ্গুয়েজে আপনি এই অ্যাপ লিখছেন, সেটি নাল-সেফ, তাই এটি আপনাকে এমন অবজেক্টের মেথড কল করতে দেবে না যা সম্ভাব্য null। তবে, এই ক্ষেত্রে, আপনি কী করছেন তা Dart-কে আশ্বস্ত করতে ! অপারেটর ("ব্যাং অপারেটর") ব্যবহার করতে পারেন। (এক্ষেত্রে displayMedium অবশ্যই নাল নয়। তবে, আমরা কেন এটি জানি তা এই কোডল্যাবের আওতার বাইরে।)
  • displayMedium-এ copyWith() কল করলে, আপনার উল্লেখ করা পরিবর্তন সহ টেক্সট স্টাইলের একটি কপি রিটার্ন করে। এই ক্ষেত্রে, আপনি শুধু টেক্সটের রঙ পরিবর্তন করছেন।
  • নতুন রঙ পেতে, আপনি আবার অ্যাপের থিম অ্যাক্সেস করুন। রঙের স্কিমের onPrimary প্রপার্টি এমন একটি রঙকে সংজ্ঞায়িত করে যা অ্যাপের প্রাইমারি রঙের উপর ব্যবহারের জন্য উপযুক্ত।

অ্যাপটি এখন নিম্নলিখিত ছবির মতো দেখতে হবে:

2405e9342d28c193.png

আপনি চাইলে, কার্ডটি আরও পরিবর্তন করুন। এখানে কয়েকটি ধারণা দেওয়া হল:

  • copyWith() আপনাকে শুধু রঙই নয়, টেক্সট স্টাইলের আরও অনেক কিছু পরিবর্তন করতে দেয়। আপনি পরিবর্তন করতে পারবেন এমন প্রপার্টির সম্পূর্ণ তালিকা পেতে, copyWith()-এর বন্ধনীর মধ্যে যেকোনও জায়গায় কার্সর রেখে Ctrl+Shift+Space (Win/Linux) বা Cmd+Shift+Space (Mac) প্রেস করুন।
  • একইভাবে, আপনি Card উইজেট সম্পর্কে আরও পরিবর্তন করতে পারবেন। যেমন, elevation প্যারামিটারের ভ্যালু বাড়িয়ে আপনি কার্ডের শ্যাডো বড় করতে পারেন।
  • রঙ নিয়ে পরীক্ষা-নিরীক্ষা করে দেখুন। theme.colorScheme.primary ছাড়াও, .secondary, .surface এবং আরও অনেক কিছু আছে। এইসব রঙের সমতুল্য onPrimary রঙ আছে।

অ্যাক্সেসিবিলিটি উন্নত করুন

Flutter ডিফল্ট হিসেবে অ্যাপ অ্যাক্সেসযোগ্য করে তোলে। যেমন, প্রতিটি Flutter অ্যাপ, টকব্যাক ও ভয়েসওভারের মতো স্ক্রিন রিডারে অ্যাপের সব টেক্সট ও ইন্টার‍্যাক্টিভ এলিমেন্ট সঠিকভাবে দেখায়।

d1fad7944fb890ea.png

তবে, কখনও কখনও কিছু কাজ করতে হয়। এই অ্যাপের ক্ষেত্রে, স্ক্রিন রিডার হয়ত কিছু জেনারেট করা শব্দ জোড়া উচ্চারণ করতে সমস্যা হতে পারে। cheaphead শব্দে থাকা দুটি শব্দ শনাক্ত করতে মানুষের কোনও সমস্যা না হলেও, স্ক্রিন রিডার শব্দের মাঝখানে থাকা ph-কে f হিসেবে উচ্চারণ করতে পারে।

একটি সমাধান হল pair.asLowerCase-এর পরিবর্তে "${pair.first} ${pair.second}" ব্যবহার করা। পরেরটি pair-এ থাকা দুটি শব্দ থেকে একটি স্ট্রিং (যেমন "cheap head") তৈরি করতে স্ট্রিং ইন্টারপোলেশন ব্যবহার করে। যৌগিক শব্দের পরিবর্তে দুটি আলাদা শব্দ ব্যবহার করলে স্ক্রিন রিডার সেগুলি সঠিকভাবে শনাক্ত করতে পারে এবং দৃষ্টিশক্তির সমস্যা যুক্ত ব্যবহারকারীদের আরও ভালো অভিজ্ঞতা প্রদান করে।

তবে, আপনি হয়ত pair.asLowerCase-এর ভিজ্যুয়াল সরলতা বজায় রাখতে চাইবেন। টেক্সট উইজেটের ভিজ্যুয়াল কন্টেন্টকে ওভাররাইড করতে Text-এর semanticsLabel প্রপার্টি ব্যবহার করুন। এটি এমন একটি সিম্যান্টিক কন্টেন্ট যা স্ক্রিন রিডারের জন্য আরও উপযুক্ত:

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),

        // ↓ Make the following change.
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }

// ...

এখন, স্ক্রিন রিডার প্রতিটি জেনারেট করা শব্দ জোড়া সঠিকভাবে উচ্চারণ করে, তবুও UI একই থাকে। আপনার ডিভাইসে স্ক্রিন রিডার ব্যবহার করে এটি করে দেখুন।

UI মাঝখানে নিয়ে আসুন

এখন যেহেতু র‍্যান্ডম শব্দ জোড়াটি যথেষ্ট ভিজ্যুয়াল ফ্লেয়ারের সাথে উপস্থাপন করা হয়েছে, তাই এটি অ্যাপের উইন্ডো/স্ক্রিনের কেন্দ্রে রাখার সময় এসেছে।

প্রথমে, মনে রাখবেন যে BigCard হল Column-এর অংশ। ডিফল্ট হিসেবে, কলাম তার চাইল্ডকে উপরের দিকে রাখে, তবে আমরা এটি ওভাররাইড করতে পারি। MyHomePage-এর build() পদ্ধতিতে যান এবং নিম্নলিখিত পরিবর্তন করুন:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,  // ← Add this.
        children: [
          Text('A random AWESOME idea:'),
          BigCard(pair: pair),
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

এটি Column-এর মধ্যে চাইল্ডকে তার প্রধান (উল্লম্ব) অক্ষ বরাবর কেন্দ্র করে।

b555d4c7f5000edf.png

চিলড্রেনগুলি আগে থেকেই কলামের ক্রস অক্ষ বরাবর কেন্দ্রিত (অন্য কথায়, সেগুলি আগে থেকেই অনুভূমিকভাবে কেন্দ্রিত)। কিন্তু Column নিজে Scaffold-এর মধ্যে কেন্দ্রে নেই। আমরা উইজেট ইন্সপেক্টর ব্যবহার করে এটি যাচাই করতে পারি।

উইজেট ইন্সপেক্টর এই কোডল্যাবের আওতার বাইরে, তবে আপনি দেখতে পাবেন যে Column হাইলাইট করা হলে, এটি অ্যাপের পুরো প্রস্থ জুড়ে থাকে না। এটি শুধুমাত্র তার চাইল্ডের জন্য যতটা অনুভূমিক স্পেস প্রয়োজন ততটাই নেয়।

আপনি শুধু কলামটিকেই মাঝখানে রাখতে পারেন। আপনার কার্সর Column-এ রাখুন, রিফ্যাক্টর মেনু (Ctrl+. বা Cmd+.-এর সাথে) কল করুন এবং সেন্টারের সাথে র‍্যাপ করুন বিকল্প বেছে নিন।

অ্যাপটি এখন নিম্নলিখিত ছবির মতো দেখতে হবে:

455688d93c30d154.png

আপনি চাইলে, এটি আরও কিছুটা পরিবর্তন করতে পারেন।

  • আপনি BigCard-এর উপরে থাকা Text উইজেট সরিয়ে দিতে পারবেন। এটি যুক্তিযুক্ত হতে পারে যে বর্ণনামূলক টেক্সট ("একটি র‍্যান্ডম দারুণ আইডিয়া:") আর প্রয়োজন নেই কারণ UI এটি ছাড়াই অর্থপূর্ণ। এবং এটি সেই উপায়ে আরও পরিষ্কার।
  • এছাড়াও, আপনি BigCard এবং ElevatedButton-এর মধ্যে SizedBox(height: 10) উইজেট যোগ করতে পারবেন। এইভাবে, দুটি উইজেটের মধ্যে একটু বেশি ব্যবধান থাকে। SizedBox উইজেট শুধু জায়গা নেয় এবং নিজে থেকে কিছু রেন্ডার করে না। এটি সাধারণত ভিজ্যুয়াল "গ্যাপ" তৈরি করতে ব্যবহার করা হয়।

ঐচ্ছিক পরিবর্তন সহ, MyHomePage-এ এই কোড আছে:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                appState.getNext();
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

// ...

এবং অ্যাপটি নিম্নলিখিত ছবির মতো দেখতে:

3d53d2b071e2f372.png

পরবর্তী বিভাগে, আপনি জেনারেট করা শব্দ পছন্দ (বা 'লাইক') করার ক্ষমতা যোগ করবেন।

৬. ফাংশনালিটি যোগ করুন

অ্যাপটি কাজ করে এবং মাঝে মাঝে এমনকি আকর্ষণীয় শব্দ জোড়াও প্রদান করে। কিন্তু ব্যবহারকারী যখনই পরবর্তী বিকল্পে ক্লিক করেন, প্রতিটি শব্দজোড়া চিরতরে অদৃশ্য হয়ে যায়। সেরা সাজেশন "মনে রাখার" কোনও উপায় থাকলে ভাল হত: যেমন, ‘লাইক' বোতাম।

e6b01a8c90df8ffa.png

বিজনেস লজিক যোগ করুন

স্ক্রল করে MyAppState-এ যান এবং নিম্নলিখিত কোড যোগ করুন:

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  // ↓ Add the code below.
  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

// ...

পরিবর্তনগুলি পরীক্ষা করুন:

  • আপনি MyAppState-এ favorites নামে একটি নতুন প্রপার্টি যোগ করেছেন। এই প্রপার্টি খালি তালিকা দিয়ে শুরু করা হয়েছে: []।
  • আপনি আরও উল্লেখ করেছেন যে তালিকায় শুধুমাত্র শব্দের জোড়া থাকতে পারে: <WordPair>[], জেনেরিক ব্যবহার করে। এটি আপনার অ্যাপকে আরও শক্তিশালী করতে সাহায্য করে—আপনি যদি এতে WordPair ছাড়া অন্য কিছু যোগ করার চেষ্টা করেন, তাহলে Dart আপনার অ্যাপ রান করতেও অস্বীকার করে। পরিবর্তে, আপনি favorites তালিকা ব্যবহার করতে পারেন, এটি জেনে যে সেখানে কখনও কোনও অবাঞ্ছিত অবজেক্ট (যেমন null) লুকানো থাকতে পারে না।
  • আপনি একটি নতুন পদ্ধতিও যোগ করেছেন, toggleFavorite(), যা পছন্দের তালিকা থেকে বর্তমান শব্দ জোড়া সরিয়ে দেয় (যদি এটি ইতিমধ্যে থাকে) বা এটি যোগ করে (যদি এটি এখনও না থাকে)। দুটি ক্ষেত্রেই, কোডটি পরে notifyListeners();-কে কল করে।

বোতাম যোগ করুন

"বিজনেস লজিক" সরিয়ে দেওয়ার পরে, এখন আবার ইউজার ইন্টারফেস নিয়ে কাজ করার সময় এসেছে। 'পরবর্তী' বোতামের বাঁদিকে 'লাইক' বোতামটি রাখতে Row প্রয়োজন। Row উইজেট হল Column-এর অনুভূমিক সমতুল্য, যা আপনি আগে দেখেছেন।

প্রথমে, আগে থেকে থাকা বোতামটি Row-এ র‍্যাপ করুন। MyHomePage-এর build() পদ্ধতিতে যান, ElevatedButton-এর উপর কার্সর রাখুন, Ctrl+. বা Cmd+.-এর সাহায্যে রিফ্যাক্টর মেনু কল-আপ করুন এবং সারি দিয়ে র‍্যাপ করুন বিকল্প বেছে নিন।

সেভ করার সময় আপনি লক্ষ্য করবেন যে Row, Column-এর মতোই কাজ করে—ডিফল্ট হিসেবে, এটি তার চাইল্ডকে বাঁদিকে একত্রিত করে। (Column এর চাইল্ডকে উপরে একত্রিত করেছে।) এটি ঠিক করতে, আপনি আগের মতো একই পদ্ধতি ব্যবহার করতে পারেন, তবে mainAxisAlignment-এর সাথে। তবে, শিক্ষামূলক (শেখার) উদ্দেশ্যে, mainAxisSize ব্যবহার করুন। এটি Row-কে উপলভ্য সব অনুভূমিক স্পেস না নিতে বলে।

নিম্নলিখিত পরিবর্তন করুন:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,   // ← Add this.
              children: [
                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

UI আগের অবস্থায় ফিরে এসেছে।

3d53d2b071e2f372.png

এরপর, লাইক বোতাম যোগ করে toggleFavorite()-এর সাথে কানেক্ট করুন। চ্যালেঞ্জের জন্য, প্রথমে নিচে দেওয়া কোড ব্লক না দেখে নিজে থেকে এটি করার চেষ্টা করুন।

e6b01a8c90df8ffa.png

নিচে যেভাবে দেখানো হয়েছে ঠিক সেইভাবে না করলেও চলবে। আসলে, আপনি যদি সত্যিই কোনও বড় চ্যালেঞ্জ না চান, তাহলে হার্ট আইকন নিয়ে চিন্তা করবেন না।

আপনি যদি এটি করতে না পারেন, তাতেও কোনও অসুবিধা নেই—কারণ, Flutter-এর সাথে এটি আপনার প্রথম ঘণ্টা।

252f7c4a212c94d2.png

MyHomePage-এ দ্বিতীয় বোতাম যোগ করার একটি উপায় এখানে দেওয়া হল। এইবার, আইকন সহ একটি বোতাম তৈরি করতে ElevatedButton.icon() কনস্ট্রাক্টর ব্যবহার করুন। এবং build পদ্ধতির উপরে, বর্তমান শব্দ জোড়াটি আগে থেকেই পছন্দের তালিকায় আছে কিনা তার উপর নির্ভর করে উপযুক্ত আইকন বেছে নিন। এছাড়াও, দুটি বোতামকে একটু দূরে রাখতে আবার SizedBox-এর ব্যবহার লক্ষ্য করুন।

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    // ↓ Add this.
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [

                // ↓ And this.
                ElevatedButton.icon(
                  onPressed: () {
                    appState.toggleFavorite();
                  },
                  icon: Icon(icon),
                  label: Text('Like'),
                ),
                SizedBox(width: 10),

                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

অ্যাপটি দেখতে এইরকম হওয়া উচিত:

ব্যবহারকারী তার পছন্দের তালিকা দেখতে পারবেন না। আমাদের অ্যাপে সম্পূর্ণ আলাদা একটি স্ক্রিন যোগ করার সময় এসে গেছে। পরের বিভাগে দেখা হচ্ছে!

৭. নেভিগেশন রেল যোগ করুন

বেশিরভাগ অ্যাপ একটি স্ক্রিনে সবকিছু দেখাতে পারে না। এই নির্দিষ্ট অ্যাপটি সম্ভবত তা করতে পারে, তবে শিক্ষামূলক উদ্দেশ্যে, আপনি ব্যবহারকারীর পছন্দের জন্য একটি আলাদা স্ক্রিন তৈরি করতে চলেছেন। দুটি স্ক্রিনের মধ্যে পাল্টাতে, আপনি প্রথম StatefulWidget প্রয়োগ করতে চলেছেন।

f62c54f5401a187.png

এই ধাপের মূল অংশে যত তাড়াতাড়ি সম্ভব পৌঁছাতে, MyHomePage-কে ২টি আলাদা উইজেটে ভাগ করুন।

MyHomePage-এর সবকটি বেছে নিয়ে মুছে দিন এবং নিম্নলিখিত কোড দিয়ে পরিবর্তন করুন:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (value) {
                print('selected: $value');
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ...

সেভ করা হলে, আপনি দেখতে পাবেন যে UI-এর ভিজ্যুয়াল দিকটি রেডি—কিন্তু এটি কাজ করে না। নেভিগেশন রেলে ♥︎ (হার্ট) আইকনে ক্লিক করলে কিছু হয় না।

388bc25fe198c54a.png

পরিবর্তনগুলি পরীক্ষা করুন।

  • প্রথমে, লক্ষ্য করুন যে MyHomePage-এর সম্পূর্ণ কন্টেন্ট একটি নতুন উইজেট, GeneratorPage-এ এক্সট্র্যাক্ট করা হয়েছে। পুরনো MyHomePage উইজেটের একমাত্র যে অংশটি এক্সট্র্যাক্ট করা যায়নি তা হল Scaffold।
  • নতুন MyHomePage-এ দুটি চাইল্ড সহ একটি Row আছে। প্রথম উইজেটটি হল SafeArea এবং দ্বিতীয়টি হল Expanded উইজেট।
  • SafeArea নিশ্চিত করে যে এর চাইল্ড হার্ডওয়্যার নচ বা স্ট্যাটাস বার দ্বারা অস্পষ্ট নয়। এই অ্যাপে, উইজেট NavigationRail-এর চারপাশে র‍্যাপ করে যাতে নেভিগেশন বোতামগুলি মোবাইল স্ট্যাটাস বারের মাধ্যমে অস্পষ্ট না হয়, উদাহরণস্বরূপ।
  • আপনি NavigationRail-এ extended: false লাইনটি পরিবর্তন করে true করতে পারবেন। এটি আইকনের পাশে লেবেল দেখায়। অ্যাপে পর্যাপ্ত অনুভূমিক স্পেস থাকলে, কীভাবে এটি অটোমেটিক করা যায় তা আপনি পরবর্তী ধাপে জানতে পারবেন।
  • নেভিগেশন রেলে দুটি গন্তব্য (হোম এবং পছন্দসই) রয়েছে, যার প্রতিটিতে সংশ্লিষ্ট আইকন এবং লেবেল রয়েছে। এটি বর্তমান selectedIndex-কেও সংজ্ঞায়িত করে। বেছে নেওয়া ইন্ডেক্স শূন্য হলে প্রথম গন্তব্য বেছে নেওয়া হয়, বেছে নেওয়া ইন্ডেক্স এক হলে দ্বিতীয় গন্তব্য বেছে নেওয়া হয় এবং এইভাবে চলতে থাকে। আপাতত, এটি শূন্যে হার্ড কোড করা আছে।
  • এছাড়াও, ব্যবহারকারী onDestinationSelected-এর মাধ্যমে কোনও একটি গন্তব্য বেছে নিলে কী হবে তা নেভিগেশন রেল নির্ধারণ করে। বর্তমানে, অ্যাপটি শুধুমাত্র print() সহ অনুরোধ করা ইন্ডেক্স ভ্যালু আউটপুট করে।
  • Row-এর দ্বিতীয় চাইল্ড হল Expanded উইজেট। এক্সপ্যান্ড করা উইজেট সারি ও কলামে অত্যন্ত উপযোগী—এগুলি আপনাকে এমন লেআউট প্রকাশ করতে দেয় যেখানে কিছু চাইল্ড শুধুমাত্র তাদের প্রয়োজনীয় জায়গা নেয় (এই ক্ষেত্রে, SafeArea) এবং অন্যান্য উইজেট যতটা সম্ভব বাকি জায়গা নেয় (এই ক্ষেত্রে, Expanded)। Expanded উইজেট সম্পর্কে ভাবার একটি উপায় হল যে সেগুলি "লোভী"। এই উইজেটের ভূমিকা আরও ভালোভাবে বুঝতে, SafeArea উইজেটটি অন্য একটি Expanded-এর সাথে র‍্যাপ করে দেখুন। ফলাফল হিসেবে পাওয়া লেআউটটি দেখতে কিছুটা এইরকম হয়:

6bbda6c1835a1ae.png

  • দুটি Expanded উইজেট নিজেদের মধ্যে উপলভ্য সব হরাইজন্টাল স্পেস ভাগ করে নেয়, যদিও নেভিগেশন রেলের জন্য বাঁদিকে সামান্য জায়গাই প্রয়োজন।
  • Expanded উইজেটের ভিতরে, একটি রঙিন Container এবং কন্টেনারের ভিতরে, GeneratorPage আছে।

স্টেটলেস বনাম স্টেটফুল উইজেট

এখনও পর্যন্ত, MyAppState আপনার রাজ্যের সব প্রয়োজন পূরণ করেছে। সেই জন্য আপনি এখনও পর্যন্ত যত উইজেট লিখেছেন সেগুলি সবই স্টেটলেস। এগুলির নিজস্ব কোনও পরিবর্তনযোগ্য অবস্থা থাকে না। কোনও উইজেট নিজেকে পরিবর্তন করতে পারে না—সেগুলিকে অবশ্যই MyAppState-এর মাধ্যমে যেতে হবে।

এটি পরিবর্তন হতে চলেছে।

নেভিগেশন রেলের selectedIndex-এর ভ্যালু ধরে রাখার জন্য আপনার কোনও উপায় প্রয়োজন। আপনি onDestinationSelected কলব্যাক থেকে এই ভ্যালু পরিবর্তন করতে চান।

আপনি MyAppState-এর আরও একটি প্রপার্টি হিসেবে selectedIndex যোগ করতে পারেন। আর এটা কাজ করবে। কিন্তু আপনি কল্পনা করতে পারেন যে প্রতিটি উইজেট যদি তার মান এতে সঞ্চয় করে তবে অ্যাপের অবস্থা দ্রুত কারণের বাইরে চলে যাবে।

e52d9c0937cc0823.jpeg

কিছু স্টেট শুধুমাত্র একটি উইজেটের সাথে প্রাসঙ্গিক, তাই এটি সেই উইজেটের সাথেই থাকা উচিত।

StatefulWidget লিখুন, এটি এক ধরনের উইজেট যাতে State আছে। প্রথমে, MyHomePage-কে স্টেটফুল উইজেটে কনভার্ট করুন।

আপনার কার্সর MyHomePage-এর প্রথম লাইনে (যেটি class MyHomePage... দিয়ে শুরু হয়) রাখুন এবং Ctrl+. বা Cmd+. ব্যবহার করে রিফ্যাক্টর মেনু কল-আপ করুন। তারপর, StatefulWidget-এ কনভার্ট করুন বিকল্প বেছে নিন।

IDE আপনার জন্য একটি নতুন ক্লাস তৈরি করে, _MyHomePageState। এই ক্লাস State-কে এক্সটেন্ড করে এবং তাই এটি নিজের ভ্যালু ম্যানেজ করতে পারে। (এটি নিজেকে পরিবর্তন করতে পারে।) এছাড়াও, লক্ষ্য করুন যে পুরনো, স্টেটলেস উইজেট থেকে build পদ্ধতিটি _MyHomePageState-এ সরানো হয়েছে (উইজেটে থাকার পরিবর্তে)। এটি হুবহু সরানো হয়েছে — build মেথডের মধ্যে কিছুই পরিবর্তন করা হয়নি। এটি এখন অন্য কোথাও বাস করে।

setState

নতুন স্টেটফুল উইজেটকে শুধু একটি ভেরিয়েবল ট্র্যাক করতে হবে: selectedIndex। _MyHomePageState-এ নিম্নলিখিত ৩টি পরিবর্তন করুন:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {

  var selectedIndex = 0;     // ← Add this property.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,    // ← Change to this.
              onDestinationSelected: (value) {

                // ↓ Replace print with this.
                setState(() {
                  selectedIndex = value;
                });

              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

// ...

পরিবর্তনগুলি পরীক্ষা করুন:

  1. আপনি একটি নতুন ভেরিয়েবল, selectedIndex, প্রবর্তন করেন এবং এটিকে 0-এ ইনিশিয়ালাইজ করেন।
  2. আপনি NavigationRail সংজ্ঞায় এই নতুন ভেরিয়েবল ব্যবহার করেন, এতদিন পর্যন্ত সেখানে থাকা হার্ড-কোডেড 0-এর পরিবর্তে।
  3. onDestinationSelected কলব্যাক কল করা হলে, কনসোলে নতুন ভ্যালু প্রিন্ট করার পরিবর্তে, আপনি এটি setState() কলের মধ্যে selectedIndex-এ অ্যাসাইন করেন। এই কলটি আগে ব্যবহার করা notifyListeners() পদ্ধতির মতোই—এটি নিশ্চিত করে যে UI আপডেট হয়েছে।

নেভিগেশন রেল এখন ব্যবহারকারীর ইন্টার‍্যাকশনে সাড়া দেয়। কিন্তু ডানদিকে বড় করা অংশটি একই থাকে। কারণ, স্ক্রিনে কী দেখানো হবে তা নির্ধারণ করতে কোডটি selectedIndex ব্যবহার করছে না।

selectedIndex ব্যবহার করুন

_MyHomePageState-এর build পদ্ধতির একদম উপরে, ঠিক return Scaffold-এর আগে নিম্নলিখিত কোডটি লিখুন:

lib/main.dart

// ...

Widget page;
switch (selectedIndex) {
  case 0:
    page = GeneratorPage();
    break;
  case 1:
    page = Placeholder();
    break;
  default:
    throw UnimplementedError('no widget for $selectedIndex');
}

// ...

কোডের এই অংশটি পরীক্ষা করুন:

  1. কোডটি Widget ধরনের একটি নতুন ভেরিয়েবল, page ঘোষণা করে।
  2. তারপর, selectedIndex-এ বর্তমান ভ্যালু অনুযায়ী, একটি সুইচ স্টেটমেন্ট page-এ একটি স্ক্রিন অ্যাসাইন করে।
  3. যেহেতু এখনও কোনও FavoritesPage নেই, তাই Placeholder ব্যবহার করুন; এটি একটি হ্যান্ডি উইজেট যা আপনি যেখানেই এটি রাখবেন সেখানে একটি ক্রস করা আয়তক্ষেত্র আঁকবে, UI-এর সেই অংশটিকে অসম্পূর্ণ হিসাবে চিহ্নিত করবে।

5685cf886047f6ec.png

  1. ফেল-ফাস্ট নীতি প্রয়োগ করে, সুইচ স্টেটমেন্ট এটিও নিশ্চিত করে যে selectedIndex-এর মান ০ বা ১ না হলে যেন একটি সমস্যা থ্রো করা হয়। এটি পরে কোনও সমস্যা হওয়া আটকায়। আপনি যদি কখনও নেভিগেশন রেলে একটি নতুন গন্তব্য যোগ করেন এবং এই কোডটি আপডেট করতে ভুলে যান, তাহলে প্রোগ্রামটি ডেভেলপমেন্টে ক্র্যাশ হয়ে যায় (কেন জিনিসগুলি কাজ করে না তা অনুমান করার পরিবর্তে, বা আপনাকে প্রোডাকশনে একটি বাগি কোড প্রকাশ করতে দেয়)।

এখন যেহেতু page-এ সেই উইজেট আছে যা আপনি ডানদিকে দেখাতে চান, তাই আপনি সম্ভবত অনুমান করতে পারেন যে অন্য কী পরিবর্তন প্রয়োজন।

বাকি থাকা একটি পরিবর্তন করার পরে _MyHomePageState-এর অবস্থা এখানে দেওয়া হল:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,
              onDestinationSelected: (value) {
                setState(() {
                  selectedIndex = value;
                });
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: page,  // ← Here.
            ),
          ),
        ],
      ),
    );
  }
}

// ...

অ্যাপটি এখন আমাদের GeneratorPage এবং প্লেসহোল্ডারের মধ্যে পরিবর্তন করে যা শীঘ্রই পছন্দসই পৃষ্ঠা হয়ে যাবে।

রেসপন্সিভনেস

এরপর, নেভিগেশন রেল রেসপন্সিভ করুন। অর্থাৎ, লেবেল দেখানোর জন্য পর্যাপ্ত জায়গা থাকলে, অটোমেটিক লেবেল দেখানোর (extended: true ব্যবহার করে) ব্যবস্থা করুন।

a8873894c32e0d0b.png

Flutter-এ এমন অনেক উইজেট আছে যা আপনার অ্যাপকে অটোমেটিক রেসপন্সিভ করে তুলতে সাহায্য করে। যেমন, Wrap হল Row বা Column-এর মতো একটি উইজেট যা পর্যাপ্ত উল্লম্ব বা অনুভূমিক স্পেস না থাকলে অটোমেটিক চিলড্রেনকে পরবর্তী "লাইন"-এ ("রান" বলা হয়) র‍্যাপ করে। FittedBox হল এমন একটি উইজেট যা আপনার স্পেসিফিকেশন অনুযায়ী উপলভ্য স্পেসে অটোমেটিক চাইল্ড ফিট করে।

কিন্তু পর্যাপ্ত জায়গা থাকলেও NavigationRail অটোমেটিক লেবেল দেখায় না কারণ প্রতিটি প্রসঙ্গে কতটা জায়গা পর্যাপ্ত তা এটি বুঝতে পারে না। ডেভেলপার হিসেবে আপনাকে এই সিদ্ধান্ত নিতে হবে।

ধরা যাক, আপনি ঠিক করেছেন যে MyHomePage কমপক্ষে ৬০০ পিক্সেল চওড়া হলে তবেই লেবেল দেখাবেন।

এই ক্ষেত্রে, ব্যবহার করার জন্য উইজেট হল LayoutBuilder। আপনার কাছে কতটা জায়গা উপলভ্য আছে তার উপর নির্ভর করে এটি আপনাকে উইজেট ট্রি পরিবর্তন করতে দেয়।

প্রয়োজনীয় পরিবর্তন করতে, VS কোডে Flutter-এর রিফ্যাক্টর মেনু আবার ব্যবহার করুন। তবে, এবার এটি একটু বেশি জটিল:

  1. _MyHomePageState-এর build মেথডের মধ্যে, Scaffold-এর উপর আপনার কার্সর রাখুন।
  2. Ctrl+. (Windows/Linux) বা Cmd+. (Mac) ব্যবহার করে রিফ্যাক্টর মেনু খুলুন।
  3. বিল্ডারের সাথে র‍্যাপ করুন বিকল্প বেছে নিয়ে Enter প্রেস করুন।
  4. নতুন যোগ করা Builder-এর নাম পরিবর্তন করে LayoutBuilder করুন।
  5. কলব্যাক প্যারামিটার তালিকা (context) থেকে (context, constraints)-এ পরিবর্তন করুন।

প্রতিবার কনস্ট্রেন্ট পরিবর্তন হলে LayoutBuilder-এর builder কলব্যাক কল করা হয়। এটি কখন হয়, যেমন:

  • ব্যবহারকারী অ্যাপের উইন্ডো ছোট বড় করেন
  • ব্যবহারকারী তার ফোন পোর্ট্রেট মোড থেকে ল্যান্ডস্কেপ মোডে বা উল্টোটা করেন
  • MyHomePage-এর পাশে কিছু উইজেটের সাইজ বড় হয়, যার ফলে MyHomePage-এর কনস্ট্রেন্ট ছোট হয়ে যায়

এখন আপনার কোড বর্তমান constraints কোয়েরি করে লেবেল দেখাবে কিনা তা সিদ্ধান্ত নিতে পারে। _MyHomePageState-এর build পদ্ধতিতে নিম্নলিখিত এক-লাইনের পরিবর্তন করুন:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: Row(
          children: [
            SafeArea(
              child: NavigationRail(
                extended: constraints.m>axWidth = 600,  // ← Here.
                destinations: [
                  NavigationRailDestination(
                    icon: Icon(Icons.home),
                    label: Text('Home'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite),
                    label: Text('Favorites'),
                  ),
                ],
                selectedIndex: selectedIndex,
                onDestinationSelected: (value) {
                  setState(() {
                    selectedIndex = value;
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                color: Theme.of(context).colorScheme.primaryContainer,
                child: page,
              ),
            ),
          ],
        ),
      );
    });
  }
}


// ...

এখন, আপনার অ্যাপ তার পরিবেশের সাথে মানানসই হয়ে কাজ করে, যেমন স্ক্রিনের সাইজ, ওরিয়েন্টেশন এবং প্ল্যাটফর্ম! অন্যভাবে বলতে গেলে, এটি রেসপন্সিভ!

এখন শুধু Placeholder-এর জায়গায় আসল পছন্দসই স্ক্রিন যোগ করতে হবে। পরবর্তী বিভাগে এই বিষয়ে আলোচনা করা হয়েছে।

৮. নতুন পৃষ্ঠা যোগ করুন

পছন্দসই পৃষ্ঠার পরিবর্তে আমরা যে Placeholder উইজেট ব্যবহার করেছি তা মনে আছে?

এটি ঠিক করার সময় এসে গেছে।

আপনি যদি অ্যাডভেঞ্চারাস বোধ করেন, তাহলে এই ধাপটি নিজে করার চেষ্টা করুন। আপনার লক্ষ্য হল নতুন স্টেটলেস উইজেট, FavoritesPage-এ favorites-এর তালিকা দেখানো এবং তারপর Placeholder-এর পরিবর্তে সেই উইজেট দেখানো।

এখানে কয়েকটি পয়েন্টার দেওয়া হল:

  • আপনি স্ক্রল করে এমন Column চাইলে, ListView উইজেট ব্যবহার করুন।
  • মনে রাখবেন, context.watch<MyAppState>() ব্যবহার করে যেকোনও উইজেট থেকে MyAppState ইনস্ট্যান্স অ্যাক্সেস করুন।
  • আপনি নতুন কোনও উইজেট ব্যবহার করে দেখতে চাইলে, ListTile-এ title (সাধারণত টেক্সটের জন্য), leading (আইকন বা অবতারের জন্য) এবং onTap (ইন্টার‍্যাকশনের জন্য)-এর মতো প্রপার্টি আছে। তবে, আপনি আগে থেকেই জানেন এমন উইজেটের মাধ্যমে একই ধরনের এফেক্ট পেতে পারেন।
  • Dart, কালেকশন লিটারেলের মধ্যে for লুপ ব্যবহার করার অনুমতি দেয়। যেমন, messages-এ স্ট্রিংয়ের তালিকা থাকলে, আপনার কাছে নিম্নলিখিত কোড থাকতে পারে:

f0444bba08f205aa.png

অন্যদিকে, আপনি যদি ফাংশনাল প্রোগ্রামিংয়ের সাথে বেশি পরিচিত হন, তাহলে Dart আপনাকে messages.map((m) => Text(m)).toList()-এর মতো কোড লিখতে দেয়। এবং অবশ্যই, আপনি সবসময় উইজেটের তালিকা তৈরি করতে এবং build মেথডের মধ্যে এটি যোগ করতে পারবেন।

আপনি নিজে পছন্দসই পৃষ্ঠা যোগ করলে, নিজের সিদ্ধান্ত নিজে নেওয়ার মাধ্যমে আরও বেশি শিখতে পারবেন। অসুবিধা হল, আপনি এমন কোনও সমস্যার সম্মুখীন হতে পারেন যা আপনি এখনও নিজে সমাধান করতে পারবেন না। মনে রাখবেন: ব্যর্থ হওয়া কোনও খারাপ বিষয় নয় এবং এটি শেখার অন্যতম গুরুত্বপূর্ণ একটি উপাদান। প্রথম ঘণ্টাতেই আপনি Flutter ডেভেলপমেন্টে দক্ষ হয়ে উঠবেন, এমনটা কেউ আশা করে না এবং আপনারও তা করা উচিত নয়।

252f7c4a212c94d2.png

পছন্দের পৃষ্ঠা প্রয়োগ করার একটি উপায় নিচে দেওয়া হল। এটি কীভাবে প্রয়োগ করা হয়েছে তা (আশা করি) আপনাকে কোড নিয়ে পরীক্ষা-নিরীক্ষা করতে—UI উন্নত করতে এবং এটিকে আপনার নিজের মতো করে তুলতে অনুপ্রাণিত করবে।

নতুন FavoritesPage ক্লাস এখানে দেওয়া হল:

lib/main.dart

// ...

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

উইজেটটি কী করে তা এখানে দেওয়া হল:

  • এটি অ্যাপের বর্তমান অবস্থা সম্পর্কে তথ্য পায়।
  • পছন্দের তালিকা খালি থাকলে, এটি মাঝখানে একটি মেসেজ দেখায়: এখনও কোনও পছন্দের আইটেম নেই।
  • অন্যথায়, এটি একটি (স্ক্রল করা যায় এমন) তালিকা দেখায়।
  • তালিকাটি একটি সারসংক্ষেপ দিয়ে শুরু হয় (যেমন, আপনার ৫টি প্রিয় আছে।)।
  • তারপর কোডটি সব পছন্দের আইটেমগুলির মাধ্যমে পুনরাবৃত্তি করে এবং প্রতিটির জন্য একটি ListTile উইজেট তৈরি করে।

এখন শুধু Placeholder উইজেটটি FavoritesPage দিয়ে পরিবর্তন করতে হবে। আর দেখুন!

আপনি GitHub-এ codelab repo-তে এই অ্যাপের ফাইনাল কোড পেতে পারেন।

৯. পরবর্তী ধাপ

অভিনন্দন!

সেরা কিছু স্মৃতি, আপনাকে ঘিরে! আপনি Column এবং দুটি Text উইজেট সহ একটি নন-ফাংশনাল স্ক্যাফোল্ড নিয়েছেন এবং এটিকে একটি রেসপন্সিভ, আনন্দদায়ক ছোট অ্যাপে পরিণত করেছেন।

d6e3d5f736411f13.png

আমরা কী কী কভার করেছি

  • Flutter কীভাবে কাজ করে তার প্রাথমিক তথ্য
  • Flutter-এ লেআউট তৈরি করা
  • ব্যবহারকারীর ইন্টার‍্যাকশন (যেমন বোতাম প্রেস করা) অ্যাপের আচরণের সাথে কানেক্ট করা
  • আপনার Flutter কোড সাজিয়ে রাখা
  • আপনার অ্যাপকে রেসপন্সিভ করা হচ্ছে
  • আপনার অ্যাপের লুক ও ফিল যাতে একই রকম থাকে তা নিশ্চিত করা

এরপর কী করতে হবে?

  • এই ল্যাবে আপনি যে অ্যাপটি লিখেছেন সেটি নিয়ে আরও পরীক্ষা-নিরীক্ষা করুন।
  • আপনি কীভাবে অ্যানিমেটেড তালিকা, গ্রেডিয়েন্ট, ক্রস-ফেড এবং আরও অনেক কিছু যোগ করতে পারেন তা দেখতে, একই অ্যাপের এই উন্নত ভার্সনের কোড দেখুন।
  • flutter.dev/learn লিঙ্কে গিয়ে আপনার শেখার জার্নি ফলো করুন।