আপনার প্রথম ফ্লটার অ্যাপ

১. ভূমিকা

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

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

আপনি যা শিখবেন

  • ফ্লাটার কীভাবে কাজ করে তার মূল বিষয়গুলো
  • ফ্লাটারে লেআউট তৈরি করা
  • ব্যবহারকারীর কার্যকলাপকে (যেমন বোতাম চাপা) অ্যাপের আচরণের সাথে সংযুক্ত করা
  • আপনার ফ্লাটার কোড সুসংগঠিত রাখা
  • আপনার অ্যাপকে রেসপন্সিভ করা (বিভিন্ন স্ক্রিনের জন্য)
  • আপনার অ্যাপের একটি সামঞ্জস্যপূর্ণ রূপ ও অনুভূতি অর্জন করা

আপনি একটি প্রাথমিক কাঠামো দিয়ে শুরু করবেন, যাতে আপনি সরাসরি আকর্ষণীয় অংশগুলোতে যেতে পারেন।

e9c6b402cd8003fd.png

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

ল্যাবটি শুরু করতে নেক্সট-এ ক্লিক করুন।

২. আপনার ফ্লাটার পরিবেশ সেট আপ করুন।

সম্পাদক

এই কোডল্যাবটিকে যথাসম্ভব সহজবোধ্য করার জন্য, আমরা ধরে নিচ্ছি যে আপনি আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট হিসেবে ভিজ্যুয়াল স্টুডিও কোড (ভিএস কোড) ব্যবহার করবেন। এটি বিনামূল্যে পাওয়া যায় এবং সব প্রধান প্ল্যাটফর্মে কাজ করে।

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

এই কোডল্যাবের জন্য আমরা ভিএস কোড (VS Code) ব্যবহার করার পরামর্শ দিই, কারণ এর নির্দেশাবলীতে ডিফল্টভাবে ভিএস কোড-এর নির্দিষ্ট শর্টকাটগুলো দেওয়া থাকে। "X কাজটি করার জন্য আপনার এডিটরে উপযুক্ত কাজটি করুন"-এর মতো কিছু বলার চেয়ে "এখানে ক্লিক করুন" বা "এই কী-টি চাপুন"-এর মতো কথা বলা বেশি সহজ।

228c71510a8e868.png

একটি উন্নয়ন লক্ষ্য নির্বাচন করুন

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

  • আইওএস
  • অ্যান্ড্রয়েড
  • উইন্ডোজ
  • ম্যাকওএস
  • লিনাক্স
  • ওয়েব

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

16695777c07f18e5.png

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

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

এখনই আপনার সিদ্ধান্ত নিন। মনে রাখবেন: আপনি পরবর্তীতে যেকোনো সময় অন্যান্য অপারেটিং সিস্টেমে আপনার অ্যাপটি চালাতে পারবেন। তবে, উন্নয়নের একটি সুস্পষ্ট লক্ষ্য মাথায় রাখলে পরবর্তী পদক্ষেপটি আরও সহজ হয়।

ফ্লাটার ইনস্টল করুন

ফ্লাটার এসডিকে ইনস্টল করার সবচেয়ে হালনাগাদ নির্দেশাবলী সর্বদা docs.flutter.dev- এ পাওয়া যায়।

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

  1. ফ্লাটার এসডিকে
  2. ফ্লাটার প্লাগইন সহ ভিজ্যুয়াল স্টুডিও কোড
  3. আপনার নির্বাচিত ডেভেলপমেন্ট টার্গেটের জন্য প্রয়োজনীয় সফটওয়্যার (উদাহরণস্বরূপ: উইন্ডোজের জন্য ভিজ্যুয়াল স্টুডিও , অথবা ম্যাকওএস-এর জন্য এক্সকোড )।

পরবর্তী অংশে আপনি আপনার প্রথম ফ্লাটার প্রজেক্ট তৈরি করবেন।

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

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

৩. একটি প্রকল্প তৈরি করুন

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

ভিজ্যুয়াল স্টুডিও কোড চালু করুন এবং কমান্ড প্যালেটটি খুলুন ( F1 অথবা Ctrl+Shift+P অথবা Shift+Cmd+P চেপে)। "flutter new" টাইপ করা শুরু করুন। Flutter: New Project কমান্ডটি নির্বাচন করুন।

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

অবশেষে, আপনার প্রজেক্টটির একটি নাম দিন। যেমন namer_app বা my_awesome_namer

260a7d97f9678005.png

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

এখন আপনি অ্যাপটির একটি প্রাথমিক কাঠামো দিয়ে ৩টি ফাইলের বিষয়বস্তু ওভাররাইট করবেন।

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

VS Code-এর বাম প্যানে Explorer সিলেক্ট করা আছে কিনা তা নিশ্চিত করুন এবং 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

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

অবশেষে, 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 Code-এর নিচের ডান কোণায় আপনি একটি বাটন দেখতে পাবেন, যেখানে বর্তমান টার্গেট ডিভাইসটি দেখানো হয়। এটি পরিবর্তন করতে ক্লিক করুন।

lib/main.dart খোলা থাকা অবস্থায়, 'play' খুঁজুন b0a5d0200af5985d.png VS Code উইন্ডোর উপরের ডানদিকের কোণায় থাকা বাটনটিতে ক্লিক করুন।

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

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),
        ],
      ),
    );

// ...

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

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

একটি বোতাম যোগ করা

এরপরে, 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 Code-এর ডিবাগ কনসোলেবাটন প্রেসড!’ মেসেজটি দেখায়।

৫ মিনিটে ফ্লাটারের একটি সংক্ষিপ্ত কোর্স

ডিবাগ কনসোল দেখাটা যতই মজার হোক না কেন, আপনি চাইবেন বাটনটি দিয়ে আরও অর্থবহ কিছু করা হোক। তবে, সেদিকে যাওয়ার আগে, এটি কীভাবে কাজ করে তা বোঝার জন্য 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 এক্সটেন্ড করে। উইজেট হলো সেইসব উপাদান যা দিয়ে প্রতিটি ফ্লাটার অ্যাপ তৈরি করা হয়। যেমনটা আপনি দেখতে পাচ্ছেন, এমনকি অ্যাপটি নিজেও একটি উইজেট।

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

lib/main.dart

// ...

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

// ...

এরপরে, MyAppState ক্লাসটি অ্যাপের স্টেট নির্ধারণ করে। ফ্লাটারে এটি আপনার প্রথম কাজ, তাই এই কোডল্যাবটি বিষয়টিকে সহজ এবং নির্দিষ্ট বিষয়ের উপর আলোকপাত করবে। ফ্লাটারে অ্যাপের স্টেট পরিচালনা করার অনেক শক্তিশালী উপায় রয়েছে। এর মধ্যে সবচেয়ে সহজে ব্যাখ্যা করা যায় এমন একটি হলো 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 নিয়ে কাজ করবেন না, কিন্তু এটি একটি সহায়ক উইজেট এবং বাস্তব জগতের অধিকাংশ ফ্লাটার অ্যাপেই এটি পাওয়া যায়।
  4. Column হলো ফ্লাটারের অন্যতম মৌলিক একটি লেআউট উইজেট। এটি যেকোনো সংখ্যক চাইল্ড গ্রহণ করে এবং সেগুলোকে উপর থেকে নিচে একটি কলামে সাজিয়ে রাখে। ডিফল্টভাবে, কলামটি তার চাইল্ডগুলোকে দৃশ্যত উপরে রাখে। আপনি শীঘ্রই এটি পরিবর্তন করে কলামটিকে কেন্দ্রে স্থাপন করবেন।
  5. আপনি প্রথম ধাপে এই Text উইজেটটি পরিবর্তন করেছেন।
  6. এই দ্বিতীয় Text উইজেটটি appState গ্রহণ করে এবং সেই ক্লাসের একমাত্র মেম্বার ` current (যা একটি WordPair ) অ্যাক্সেস করে। WordPair বেশ কিছু দরকারি গেটার প্রদান করে, যেমন asPascalCase বা asSnakeCase । এখানে আমরা asLowerCase ব্যবহার করেছি, কিন্তু আপনার পছন্দ অনুযায়ী বিকল্পগুলোর কোনোটি এখনই পরিবর্তন করতে পারেন।
  7. লক্ষ্য করুন, ফ্লাটার কোডে কীভাবে ট্রেলিং কমার ব্যাপক ব্যবহার করা হয়। এই নির্দিষ্ট কমাটির এখানে থাকার কোনো প্রয়োজন নেই, কারণ children হলো এই নির্দিষ্ট Column প্যারামিটার তালিকার শেষ (এবং একমাত্র ) সদস্য। তবুও, সাধারণত ট্রেলিং কমা ব্যবহার করা একটি ভালো অভ্যাস: এগুলো আরও সদস্য যোগ করাকে অত্যন্ত সহজ করে তোলে এবং ডার্টের অটো-ফরমেটারকে সেখানে একটি নিউলাইন বসানোর জন্য ইঙ্গিত হিসেবেও কাজ করে। আরও তথ্যের জন্য, `Code formatting` দেখুন।

এরপরে, আপনি বাটনটিকে স্টেটের সাথে সংযুক্ত করবেন।

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

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) । এটিকে আরও জটিল কিছুতে পরিবর্তন করতে চাইলে, এই লাইনটিকে একটি আলাদা উইজেটে নিয়ে যাওয়া ভালো। আপনার 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 কে নির্দেশ করে না।

এখন, রিফ্যাক্টর মেনুটি খুলুন। ভিএস কোডে, আপনি দুটি উপায়ের একটিতে এটি করতে পারেন:

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

অথবা

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

রিফ্যাক্টর মেনু থেকে এক্সট্র্যাক্ট উইজেট নির্বাচন করুন। বিগকার্ড- এর মতো একটি নাম দিন এবং 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 উইজেটের Refactor মেনুটি খুলুন। তবে, এবার আপনি উইজেটটি এক্সট্র্যাক্ট করবেন না।

এর পরিবর্তে, 'Wrap with Padding ' নির্বাচন করুন। এটি Text উইজেটটির চারপাশে Padding নামে একটি নতুন প্যারেন্ট উইজেট তৈরি করে। সেভ করার পর, আপনি দেখতে পাবেন যে র‍্যান্ডম শব্দটি ইতিমধ্যেই আরও বেশি ফাঁকা জায়গা পেয়েছে।

ডিফল্ট মান 8.0 থেকে প্যাডিং বাড়ান। উদাহরণস্বরূপ, আরও প্রশস্ত প্যাডিংয়ের জন্য 20 মতো একটি মান ব্যবহার করুন।

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

এর মাধ্যমে আপনি প্যারেন্ট উইজেট নির্দিষ্ট করতে পারবেন। 'Card' টাইপ করে এন্টার চাপুন।

এটি 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 এর জন্য সিড কালারটি পরিবর্তন করে এই রঙটি এবং পুরো অ্যাপটির কালার স্কিম পরিবর্তন করতে পারেন।

লক্ষ্য করুন, রঙটি কত মসৃণভাবে পরিবর্তিত হচ্ছে। একে ইমপ্লিসিট অ্যানিমেশন বলা হয়। ফ্লাটারের অনেক উইজেট বিভিন্ন মানের মধ্যে মসৃণভাবে ইন্টারপোলেট করে, যাতে ইউজার ইন্টারফেসটি (UI) বিভিন্ন অবস্থার মধ্যে শুধু "লাফিয়ে" না যায়।

কার্ডের নিচে থাকা উঁচু বাটনটিরও রঙ বদলে যায়। নির্দিষ্ট মান কোড করার পরিবর্তে অ্যাপ-ব্যাপী Theme ব্যবহারের এটাই সুবিধা।

টেক্সট থিম

কার্ডটিতে এখনও একটি সমস্যা আছে: লেখাগুলো খুব ছোট এবং এর রঙ পড়া কঠিন। এটি ঠিক করতে, 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 প্রপার্টিটি হলো একটি বড় স্টাইল যা টেক্সট প্রদর্শনের জন্য ব্যবহৃত হয়। এখানে 'display' শব্দটি টাইপোগ্রাফিক অর্থে ব্যবহৃত হয়েছে, যেমন 'display typeface '। displayMedium এর ডকুমেন্টেশনে বলা হয়েছে যে, "ডিসপ্লে স্টাইলগুলো সংক্ষিপ্ত ও গুরুত্বপূর্ণ টেক্সটের জন্য সংরক্ষিত"—যা আমাদের ব্যবহারের ক্ষেত্রটির সাথে হুবহু মিলে যায়।
  • থিমের displayMedium প্রপার্টিটি তাত্ত্বিকভাবে null হতে পারে। ডার্ট, যে প্রোগ্রামিং ল্যাঙ্গুয়েজে আপনি এই অ্যাপটি লিখছেন, তা null-safe, তাই এটি আপনাকে সম্ভাব্য null অবজেক্টের মেথড কল করতে দেবে না। তবে, এই ক্ষেত্রে, আপনি ডার্টকে এটা নিশ্চিত করতে ! অপারেটর ("ব্যাং অপারেটর") ব্যবহার করতে পারেন যে আপনি কী করছেন সে সম্পর্কে অবগত আছেন। (এই ক্ষেত্রে displayMedium অবশ্যই null নয় । যদিও, আমরা কেন এটা জানি তা এই কোডল্যাবের আওতার বাইরে।)
  • displayMedium এর উপর copyWith() কল করলে, আপনার নির্ধারিত পরিবর্তনসহ টেক্সট স্টাইলের একটি অনুলিপি ফেরত আসে। এক্ষেত্রে, আপনি শুধুমাত্র টেক্সটের রঙ পরিবর্তন করছেন।
  • নতুন রঙটি পেতে, আপনাকে আবারও অ্যাপের থিমে প্রবেশ করতে হবে। কালার স্কিমের ' onPrimary প্রপার্টিটি এমন একটি রঙ নির্ধারণ করে, যা অ্যাপের প্রাইমারি কালার হিসেবে ব্যবহারের জন্য উপযুক্ত।

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

2405e9342d28c193.png

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

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

প্রবেশগম্যতা উন্নত করুন

ফ্লাটার অ্যাপগুলোকে ডিফল্টভাবেই অ্যাক্সেসযোগ্য করে তোলে। উদাহরণস্বরূপ, প্রতিটি ফ্লাটার অ্যাপ TalkBack এবং VoiceOver-এর মতো স্ক্রিন রিডারগুলোর কাছে অ্যাপের সমস্ত টেক্সট এবং ইন্টারেক্টিভ এলিমেন্ট সঠিকভাবে প্রদর্শন করে।

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 উপর রাখুন, Refactor মেনুটি খুলুন ( Ctrl+. বা Cmd+. চেপে), এবং ‘Wrap with Center’ নির্বাচন করুন।

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

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

পরবর্তী অংশে, আপনি তৈরি হওয়া শব্দগুলোকে পছন্দের তালিকায় (বা 'লাইক') রাখার সুবিধা যোগ করবেন।

৬. কার্যকারিতা যোগ করুন

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

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();
  }
}

// ...

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

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

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

'বিজনেস লজিক'-এর কাজ শেষ, এবার আবার ইউজার ইন্টারফেস নিয়ে কাজ করার পালা। 'নেক্সট' বাটনের বাম পাশে 'লাইক' বাটনটি রাখতে একটি ' Row প্রয়োজন। ' Row উইজেটটি হলো Column -এর (Column) হরাইজন্টাল সংস্করণ, যা আপনি আগেই দেখেছেন।

প্রথমে, বিদ্যমান বাটনটিকে একটি Row এর মধ্যে রাখুন। MyHomePage এর build() মেথডে যান, ElevatedButton এর উপর আপনার কার্সর রাখুন, Ctrl+. বা Cmd+. চেপে Refactor মেনুটি খুলুন এবং Wrap with Row নির্বাচন করুন।

সেভ করার সময় আপনি লক্ষ্য করবেন যে, 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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

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

3d53d2b071e2f372.png

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

e6b01a8c90df8ffa.png

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

ব্যর্থ হওয়াটাও সম্পূর্ণ স্বাভাবিক—সর্বোপরি, ফ্লাটারের সাথে এটা আপনার প্রথম ঘণ্টা।

252f7c4a212c94d2.png

MyHomePage এ দ্বিতীয় বাটনটি যোগ করার একটি উপায় এখানে দেওয়া হলো। এবার, একটি আইকনসহ বাটন তৈরি করতে ElevatedButton.icon() কনস্ট্রাক্টরটি ব্যবহার করুন। এবং build মেথডের শুরুতে, বর্তমান শব্দজোড়াটি আগে থেকেই favorites-এ আছে কি না, তার উপর নির্ভর করে উপযুক্ত আইকনটি বেছে নিন। এছাড়াও, দুটি বাটনকে কিছুটা দূরে রাখার জন্য আবারও 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 উইজেট। 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+. ব্যবহার করে Refactor মেনুটি খুলুন। এরপর, Convert to 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. এখন পর্যন্ত ব্যবহৃত হার্ড-কোডেড 0 এর পরিবর্তে, আপনি NavigationRail সংজ্ঞায় এই নতুন ভেরিয়েবলটি ব্যবহার করবেন।
  3. যখন onDestinationSelected কলব্যাকটি কল করা হয়, তখন নতুন মানটি কেবল কনসোলে প্রিন্ট না করে, একটি setState() কলের ভিতরে সেটিকে selectedIndex এ অ্যাসাইন করা হয়। এই কলটি পূর্বে ব্যবহৃত notifyListeners() মেথডের মতোই—এটি UI-এর আপডেট নিশ্চিত করে।

ন্যাভিগেশন রেলটি এখন ব্যবহারকারীর ইন্টারঅ্যাকশনে সাড়া দিচ্ছে। কিন্তু ডানদিকের প্রসারিত অংশটি একই থাকছে। এর কারণ হলো, কোন স্ক্রিনটি প্রদর্শিত হবে তা নির্ধারণ করতে কোডটি 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

ফ্লাটার বেশ কিছু উইজেট সরবরাহ করে যা আপনার অ্যাপগুলোকে স্বয়ংক্রিয়ভাবে রেসপন্সিভ করতে সাহায্য করে। উদাহরণস্বরূপ, Wrap হলো Row বা Column এর মতো একটি উইজেট যা উল্লম্ব বা অনুভূমিকভাবে পর্যাপ্ত জায়গা না থাকলে চাইল্ডগুলোকে স্বয়ংক্রিয়ভাবে পরবর্তী "লাইনে" (যাকে "run" বলা হয়) নিয়ে যায়। এছাড়াও রয়েছে FittedBox , এমন একটি উইজেট যা আপনার নির্দিষ্ট নির্দেশনা অনুযায়ী তার চাইল্ডকে উপলব্ধ জায়গায় স্বয়ংক্রিয়ভাবে ফিট করে দেয়।

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

ধরুন আপনি সিদ্ধান্ত নিয়েছেন যে, MyHomePage প্রস্থ কমপক্ষে ৬০০ পিক্সেল হলেই কেবল লেবেলগুলো দেখানো হবে।

এক্ষেত্রে ব্যবহার করার উইজেটটি হলো LayoutBuilder )। এটি আপনাকে আপনার উপলব্ধ জায়গার পরিমাণের ওপর নির্ভর করে উইজেট ট্রি পরিবর্তন করার সুযোগ দেয়।

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

  1. _MyHomePageState এর build মেথডের ভিতরে, Scaffold উপর আপনার কার্সর রাখুন।
  2. Ctrl+. (Windows/Linux) অথবা Cmd+. (Mac) চেপে Refactor মেনুটি খুলুন।
  3. Wrap with Builder নির্বাচন করুন এবং 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.maxWidth >= 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 (ইন্টার‍্যাকশনের জন্য)-এর মতো প্রোপার্টি রয়েছে। তবে, আপনি আপনার পরিচিত উইজেটগুলো দিয়েই একই ধরনের ফলাফল পেতে পারেন।
  • ডার্টে কালেকশন লিটারেলের ভেতরে for লুপ ব্যবহার করা যায়। উদাহরণস্বরূপ, যদি messages কালেকশনে স্ট্রিং-এর একটি লিস্ট থাকে, তাহলে আপনি নিচের মতো কোড লিখতে পারেন:

f0444bba08f205aa.png

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

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

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 দিয়ে প্রতিস্থাপন করতে হবে। আর ব্যস!

এই অ্যাপটির চূড়ান্ত কোড আপনি গিটহাবের কোডল্যাব রিপোজিটরিতে পেয়ে যাবেন।

৯. পরবর্তী পদক্ষেপ

অভিনন্দন!

বাহ! আপনি একটি Column এবং দুটি Text উইজেটসহ একটি অকার্যকর কাঠামোকে একটি রেসপন্সিভ ও চমৎকার ছোট্ট অ্যাপে পরিণত করেছেন।

d6e3d5f736411f13.png

আমরা যা আলোচনা করেছি

  • ফ্লাটার কীভাবে কাজ করে তার মূল বিষয়গুলো
  • ফ্লাটারে লেআউট তৈরি করা
  • ব্যবহারকারীর কার্যকলাপকে (যেমন বোতাম চাপা) অ্যাপের আচরণের সাথে সংযুক্ত করা
  • আপনার ফ্লাটার কোড সুসংগঠিত রাখা
  • আপনার অ্যাপকে রেসপন্সিভ করা
  • আপনার অ্যাপের একটি সামঞ্জস্যপূর্ণ রূপ ও অনুভূতি অর্জন করা

এরপর কী?

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