<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Adnan Kamali | عدنان کمالی</title>
        <link>https://virgool.io/feed/@adnankamali1248</link>
        <description>من عدنان کمالی چاهوئی هستم و برنامه نویس و درحال تبدیل شدن به مهندس کامپیوتر</description>
        <language>fa</language>
        <pubDate>2026-04-15 06:45:13</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/3853872/avatar/afYdSF.jpg?height=120&amp;width=120</url>
            <title>Adnan Kamali | عدنان کمالی</title>
            <link>https://virgool.io/@adnankamali1248</link>
        </image>

                    <item>
                <title>استفاده از Flavors در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-flavors-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-ss3wl83lztug</link>
                <description>خروجی متفاوت با Flavors در فلاتربرای اینکه بتونیم توی فلاتر با یک کدبیس نسخه های اپ رو برای سناریو های مختلف بسازیم از Flavors استفاده میکنیم.این Flavor به چه کاری میاد؟فرض کنید درحال توسعه اپلیکیشن با فلاتر هستید و تیم ما شامل برنامه نویس ها  برای توسعه فیچر ها، تیم QA برای برسی و باگ گیری و تیم عملیاتی برای انتشار نسخه نهایی که هر تیم باید با محیط و تنظیمات جدا کار کنه به این شکل کهسرور تست داخلی + ابزارهای دیباگ فعال:Development (dev)محیط نزدیک به واقیت + گزارش گیری فعال :Staging (staging)محیط واقعی + امنیت و پرفورمنس کامل :Production (prod)که این اسپلیت کردن با flavors قابل انجام هستحالا چرا Flavors مهمه؟بدون flavor یه سری خطا های احتمالی وجود داره انتشار اپلیکیشنی که اتصال به API که روی حالت dev هست امکان داره روی حالت پروداکشن لاگ هارو غیرفعال نکرده باشی و ...ولی اگر از flavors استفاده کنیم از این مشکلات جلوگیری کنیم و توی CI/CD هم بهت خیلی راحت کمک میکنه که نسخه های مختلف رو خروجی بدیپیکربندی Flavor در فلاترگام اول توی پوشه lib سه نقطه ورودی میسازیم که شاملlib/
 ├── main_dev.dart
 ├── main_staging.dart
 ├── main_prod.dart
 └── main_common.dartاینجا سه نقطه ورودی داریم main_dev.dart, main_staging.dart, main_prod.dart که اجرا کننده فایل main_common.dart هستحالا کد های main_common.dart ببینیمimport &#039;package:flutter/material.dart&#039;;
import &#039;src/app.dart&#039;;
import &#039;src/config/app_config.dart&#039;;
Future&lt;void&gt; mainCommon({required String env}) async {
  WidgetsFlutterBinding.ensureInitialized();
  final config = AppConfig.forEnv(env);
  runApp(MyApp(config: config));
}یه متود mainCommon ساختیم که یک String میگیره با نام env که محیط مارو مشخص میکنه یه کانفیگ ساختیم (کدشو جلوتر میبینی) و اپ اجرا میشه با کانفیگ مورد نظربرای مثال فایل main_dev.dart رو ببینیم که برای بقیه ورودی ها هم به همین شکل هستimport &#039;main_common.dart&#039;;
void main() {
  mainCommon(env: &#039;dev&#039;); // dev, staging, prod
}خب بریم سراغ کد کانفیگ import &#039;package:flutter/material.dart&#039;;

class AppConfig {
  final String env;
  final String appName;
  final String baseUrl;
  final Color primaryColor;

  const AppConfig({
    required this.env,
    required this.appName,
    required this.baseUrl,
    required this.primaryColor,
  });

  factory AppConfig.forEnv(String env) {
    switch (env) {
      case &#039;dev&#039;:
        return const AppConfig(
          env: &#039;dev&#039;,
          appName: &#039;My App Dev&#039;,
          baseUrl: &#039;https://dev.api.myapp.ir&#039;,
          primaryColor: Colors.red,
        );
      case &#039;staging&#039;:
        return const AppConfig(
          env: &#039;staging&#039;,
          appName: &#039;My App Staging&#039;,
          baseUrl: &#039;https://staging.api.myapp.ir&#039;,
          primaryColor: Colors.orange,
        );
      default:
        return const AppConfig(
          env: &#039;prod&#039;,
          appName: &#039;My App&#039;,
          baseUrl: &#039;https://api.myapp.ir&#039;,
          primaryColor: Colors.green,
        );
    }
  }
}
توی این کلاسی که ساختیم appname، baseUrl و primaryColor رو برای هر محیط کاستومیایز کردیم حالا وقتشه که Flavor رو توی اندروید تنظیم کنیم. برای این کار نیازه بریم به مسیر android/app/build.gradle.ktsو کد زیرandroid {
    ...
    flavorDimensions += &quot;env&quot;
    productFlavors {

            create(&quot;dev&quot;) {
                dimension = &quot;env&quot;
                applicationIdSuffix = &quot;.dev&quot;
            }
            create(&quot;staging&quot;) {
                dimension = &quot;env&quot;
                applicationIdSuffix = &quot;.staging&quot;
            }
            create(&quot;prod&quot;) {
                dimension = &quot;env&quot;
            }

    }
    ...
}
سه productFlavors ساختیم که dev, staging و prod که برای هر خروجی به آخر package name ما applicationIdSuffix اضافه میکنه که این موجب میشه اپلیکیشن بصورت آپدیت نیاد و جداگانه کلون بشهتوجه کنید که برای prod من هیچ applicationIdSuffix نگذاشتم چون نباید package name عوض بشهاجرای اپلیکیشن با استفاده از flovars# محیط توسعه
flutter run --flavor dev -t lib/main_dev.dart

# محیط تست (QA)
flutter run --flavor staging -t lib/main_staging.dart

# محیط انتشار
flutter run --flavor prod -t lib/main_prod.dart
می‌توانید برای هر flavor:آیکون و Splash Screen متفاوت داشته باشید (با flutter_launcher_icons و flutter_native_splash)تنظیمات امنیتی متفاوت اعمال کنید.Feature Flag و dart-define برای فعال/غیرفعال کردن فیچرها استفاده کنید.خب...استفاده از flavor در Flutter یکی از بهترین روش‌ها برای مدیریت چند محیط (dev/staging/prod) در یک کدبیسه. این کار باعث می‌شه تیم شما:سریع‌تر نسخه‌های مختلف را بسازهاز اشتباهات خطرناک جلوگیری کنهفرآیند تست و انتشار را بهینه کنهامیدوارم که از این مطلب استفاده کرده باشید منابع من برای این نوشته : pub.dev             عدنان کمالی | Adnan Kamali        </description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 15 Aug 2025 17:58:13 +0330</pubDate>
            </item>
                    <item>
                <title>چطوری ازgRPC توی فلاتر استفاده کنیم (پارت دوم)</title>
                <link>https://virgool.io/@adnankamali1248/%DA%86%D8%B7%D9%88%D8%B1%DB%8C-%D8%A7%D8%B2grpc-%D8%AA%D9%88%DB%8C-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DA%A9%D9%86%DB%8C%D9%85-%D9%BE%D8%A7%D8%B1%D8%AA-%D8%AF%D9%88%D9%85-lu7c4rolmeq1</link>
                <description>فراخوانی تابع با فلاتر با gRPCتوی بخش اول یک سرور gRPC رو با پایتون بالا اوردیم و روی پورت 50051 بالا اوردیم که پروتکل ما به اسم helloworld.proto این بود syntax = &quot;proto3&quot;;
package helloworld;
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}حالا میخوایم توی فلاتر متود SayHello رو صدا بزنیم و ریکوئست ما (یا همون آرگیومنت ورودی تابع ما) name خواهد بود و جواب (مقدار خروجی تابع ما) یک message هست.از کجا شروع کنیم؟اول از همه باید پروتوکل بافر یا همون protoc رو باید داشته باشیم که توی پارت اول گفتم و پکیج های مورد نیازمون رو اضافه میکنیم که پکیج ها grpc و protobuf هست که با دستور زیر اضافه میکنیمflutter pub add grpc protobufپلاگین protobuf هم برای دارت نصب میکنیم با درستور زیرdart pub global activate protoc_pluginتولید کد دارت از فایل .protoبا استفاده از protoc که دانلود کردیم، فایل های دارت رو از روی فایل .proto خودمون که اسمش helloworld.proto هست با دستور زیر تولید می کنیمprotoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto:dart_out=grpc-- یعنی هم messageها و هم کلاس‌های gRPC تولید بشن.خروجی میره تو lib/src/generated.بعد از این، دو فایل ایجاد میشه:helloworld.pb.dart (تعریف messageها)helloworld.pbgrpc.dart (تعریف client/service)اتصال به سرور و فراخوانی تابع SayHelloحالا بعد از اجرای کد بالا کد های gRPC ما تولید شدن توی lib/src/generated import &#039;package:grpc/grpc.dart&#039;;
import &#039;src/generated/helloworld.pb.dart&#039;;
import &#039;src/generated/helloworld.pbgrpc.dart&#039;;

Future&lt;void&gt; callSayHello(String name) async {
  final channel = ClientChannel(
    &#039;localhost&#039;, // ip address
    port: 50051,
    options: const ChannelOptions(
      credentials: ChannelCredentials.insecure(), // بدون TLS
    ),
  );

  final stub = GreeterClient(channel);

  try {
    // Calling the sayHello method
    final response = await stub.sayHello(
      HelloRequest()..name = name,
    );
    print(&#039;Server replied: ${response.message}&#039;);
  } catch (e) {
    print(&#039;Caught error: $e&#039;);
  }
  
  await channel.shutdown();
}
توی این کد اول از همه پکیج grpc رو ایمپورت می کردیم و اون فایل های تولید شده یه تابع ساختم به نام callSayHello که آرگیومنت name به عنوان String میگیره که توی خط 19 جاگذاری کردمتوی این تابع یک چنل ساختم تا به سرور پایتون وصل بشیم&#039;localhost&#039; آدرس سرور هست (روی موبایل باید IP کامپیوترت رو بزنی).port: 50051 همون پورتیه که در سرور پایتون باز کردیم.ChannelOptions تنظیمات اتصال رو مشخص می‌کنه:credentials: ChannelCredentials.insecure یعنی بدون رمزگذاری (TLS) وصل شو. چون سرور پایتون ما امن نشده.این کانال مثل یک خط تلفن هست که همه‌ی درخواست‌ها از طریقش رد و بدل می‌شن.خط 14 یک کلاینت از کلاس GreeterClient ساختم که توسط protoc و pbgrpc.dart تولید شده.این کلاس همه‌ی متدهای RPC (مثل sayHello) رو به شکل توابع Dart در اختیارمون می‌ذاره.stub یعنی واسطه‌ای که از طریقش با سرور حرف می‌زنیم.توی بلوک try میاد متود sayHello رو صدا میزنیم با آریگومنت name که با استفاده از ..(دونقطه) مقداری که از ورودی تابع callSayHello گرفتیم رو ست میکنیمو در نهایت خط 26 کانال رو می بندیم تا ارتباط آزاد بشه و منابع شبکه اشغال نمونهخب نتیجه میشه اینcallSayHello(&quot;Adnan Kamali&quot;);
// اگر موفقیت آمیز بود
Server replied: Hello, Adnan Kamali!دوتا نکته:1- اگر از موبایل برای تست استفاده میکنی یادت نره که دسترسی اینترنت رو توی AndroidManifest.xml بدی2- اگر قراره که دیوایس های خارج از سرورت (کامپیوتر) تست بزنی حتما با ipconfig(توی ویندوز) ایپی ipv4 رو قرار بدی که فرمتش به شکل 192.168... بجای localhost هستامیدوارم که این مطلب به درتون خورده باشهعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 08 Aug 2025 18:23:01 +0330</pubDate>
            </item>
                    <item>
                <title>چطوری ازgRPC توی فلاتر استفاده کنیم (پارت اول)</title>
                <link>https://virgool.io/@adnankamali1248/%DA%86%D8%B7%D9%88%D8%B1%DB%8C-%D8%A7%D8%B2grpc-%D8%AA%D9%88%DB%8C-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%DA%A9%D9%86%DB%8C%D9%85-%D9%BE%D8%A7%D8%B1%D8%AA-%D8%A7%D9%88%D9%84-nfielatarewp</link>
                <description>کار با gRPC در فلاتردر پارت اول میخوام یکم درمورد gRPC توضیح بدم و یک gRPC رو با پایتون بیارم بالا و در پارت دوم از طریق اپلیکیشن فلاتری متودی که توی پایتون ساختیم رو با فلاتر فراخوانی کنیماصلا RPC چی هست که gRPC چی باشه؟RPC یا Remote Procedure Call به معنی فراخوانی تابع از راه دور هست که توسعه دهنده ها میتونن بجای ارسال پیام های json که بر پایه http ورژن یک هست بیان و متود ها یا توابع هایی رو تعریف کنند و خیلی راحت فراخوانی کنند.حالا gRPC چیه؟یک چارچوب یا همون framework متن باز برای سیستم های RPC با کارایی بالا که توسط گوگل توسعه یافتهاین gRPC چطوری کار میکنه؟اول از همه باید با استفاده از زبان پروتکل بافر که با فرمت proto. هست پیام ها و سرویس هامون رو مشخص و دیفاین میکنممرحله دوم هم تولید کد های هر زبان هست که زبان های مختلف از جمله دارت و پایتون رو پشتیبانی میکنه که برای تولید کد هر زبان نیاز به ابزار protoc داریم که بیاد این پروتوکلی که نوشتیم رو به زبان های مقصد تبدیل کنهاین gRPC چه مزیتی داره؟کارایی بالا که از HTTP/2 استفاده میکنه و مقادیر رو بصورت باینری جابه جا میکنه که حجم داده و سرعت انتقال نسب به json بالاترهاز زبان های مختلف استفاده میکنه، اینطوریه که شما یک proto مینویسید و برای زبان های مختلف خروجی میگریدgRPC کجا ها استفاده میشه؟معمولا gRPC توی میکروسرویس ها که نیاز به سرعت زیاد جهت جابه جایی انتقال و توی میکروسرویس ها یا هرجایی که از چند زبان برنامه نویسی استفاده میشه gRPC کمک کننده هست چون فقط یک پروتکل داری و استفاده میکنیبریم یک پروتکل بافر ساده باهم بسازیمخب من فایلی به نامه helloworld.proto رو میسازم و توش پروتکل هامو تعریف میکنمsyntax = &quot;proto3&quot;;
package helloworld;
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}اول از همه اومدم گفتم که از ورژن سه پروتکل بافر استفاده میکنم.اسم پکیجم رو گفتم که این مورد توی ایمپورت کردن کد تولید شده بهمون کمک میکنه که پکیج ما helloworld هستاومدم یک سرویس Greeter نوشتم که یک متود rpc به نام SayHello داره که ورودی این متود HelloRequest و خروجی اون HelloReply هستیک پیام یا بهتره بگم یه ساختار داده به نام HelloRequest تعریف کردم که فقط یک فیلد داره و اون هم name هست از نوع string و ایندکس یک که برای سریالایز شدن استفاده میشهاین ایندکس باید یکتا باشه و بهتره از یک شروع بشهیک پیام پاسخ به نام HelloReply تعریف کردم که message رو که string هست به عنوان پاسخ بر میگردونهتولید کد برای پایتون از روی پروتکلی که ساختیمبرای تولید کد اول از همه باید کامپایلر پروتکل بافر یعنی ptoco رو نصب که کنیم که از لینک گیت هاب قابل دانلود هست و بعد برای پایتون پکیج های grpcio, grpcio-tools با دستور pip نصب میکنیم و بعد دستور زیر رو اجرا میکنیم توی همون پوشه ای که helloworld.proto هستpython -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto-I. می‌گه که فایل .proto در مسیر جاری هست.--python_out=. فایل‌های protobuf (ساختار پیام‌ها) را تولید می‌کنه.--grpc_python_out=. کدهای لازم برای gRPC client/server را تولید می‌کنه.helloworld.proto اسم فایل شماست.بعد از اجرای دستور، دو فایل جدید کنار فایل proto تولید می‌شن:helloworld_pb2.py: تعریف پیام‌ها (HelloRequest، HelloReply)helloworld_pb2_grpc.py: تعریف سرویس gRPC (GreeterStub، GreeterServicer)راه اندازی سرویسمون توی پایتوناین کد راه اندازی هست و خط به خط بهت میگم چیکار کردم
from concurrent import futures
import grpc
import helloworld_pb2, helloworld_pb2_grpc

class Greeter(helloworld_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message=f&quot;Hello, {request.name}!&quot;)
def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port(&#039;[::]:50051&#039;)
    server.start()
    server.wait_for_termination()
if name == &#039;__main__&#039;:
    serve()تو خط اول ماژول concurrent.futures یکی از ابزارهای استاندارد پایتون برای مدیریت thread و pool از thread‌هاست.اینجا برای راه‌اندازی سرور gRPC با امکان پردازش چند درخواست هم‌زمان (multi-threading) استفاده می‌شه.ماژول اصلی grpc به همراه کد های تولید شده رو ایمپورت کردمیه کلاس ساختم به اسم Greeter و میاد از سرویسی که ساختم توی proto و تولید شده توی فایل helloworld_pb2_grpc که به نام GreeterServicer ارث بری میکنم و متودی که توی proto تعریف کردم رو برای خودم تعریف میکنم که متود SayHello بودrequest: شی‌ای از نوع HelloRequest که کلاینت فرستاده.context: اطلاعات اضافه مربوط به وضعیت فراخوانی (مثلاً deadline، metadata، status و ...).در پاسخ، یک HelloReply تولید می‌کنیم و داخلش پیام Hello, {name}! رو قرار می‌دیم.مثلاً اگر کلاینت &quot;name = &quot;Adnan Kamali بفرسته، پاسخ سرور می‌شه: &quot;!Hello, Adnan Kamali&quot;تابعی به نام serve تعریف شده که کارش راه‌اندازی سرور gRPC هست و یک شیء سرور gRPC می‌سازیم و بهش یک thread pool با حداکثر 10 thread می‌دیم تا بتونه هم‌زمان چند درخواست رو هندل کنه.توی خط 11 کلاس Greeter که ساختیم رو به سرور gRPC اضافه میکنیم و این خط دقیقا همون لحظه ایه که میگیم اگه کسی RPC به اسم SayHello صدا زد، تابع SayHello رو اجرا کنخط 12 سرور میاد روی پورت 50051 روی تمامی آی پی های ماشین شنود میشهخط 13 سرویس شروع و در خط 14 سرور تا زمانی که دستی بسته نشه با همون ctrl+c درحالت اجرا میمونهامیدوارم تا اینجا مطلب براتون روشن شده باشه و اگر بکند کار نیستید حداقل درمورد gRPC یکم اطلاعاتتون بیشتر شده باشه و در پارت دوم درمورد چطوری فراخوانی تابع SayHello با استفاده از فلاتر رو توضیح میدمعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Thu, 07 Aug 2025 22:20:24 +0330</pubDate>
            </item>
                    <item>
                <title>جستجو حرفه ای با RxDart و Bloc در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%AC%D8%B3%D8%AA%D8%AC%D9%88-%D8%AD%D8%B1%D9%81%D9%87-%D8%A7%DB%8C-%D8%A8%D8%A7-rxdart-%D9%88-bloc-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-h290bslqt8m6</link>
                <description>جستجو حرفه ای با ریکتیو دارت و بلاک در فلاتراول از همه اگر با استریم ها و بلاک توی دارت و فلاتر آشنا نیستی بدی نیست که بری دربارش بخونیدوم اینکه هدف من از این نوشته، نوشتن bloc هست که با تاخیر اون Event که ارسال میشه اجرا بشه که این تاخیر بخاطر وارد کردن کلمه توسط کاربر هستسوم اینکه مثالی که میزنم فقط bloc رو پوشش میده و فقط مفاهیم رو توضیح میدمRxDart یا ریکتیو دارت اصلا چی هست؟بطور خلاصه و خیلی سریع بخوام بگم، RxDart یه کتابخونه ایه که قابلیت برنامه نویسی واکنش گرا رو بهمون میده یه یکم ساده تر میاد روی استریم ها یسری متود های اضافه بهمون میده، لیستشو میتونی اینجا ببینی که زیاد هم هست و ما در این نوشته از چهارتا متود اون استفاده میکنیم که شاملdebounceTimeflatMapswitchMapMergeStreamاین متود ها چیکار میکنن؟debounceTime همینطو که از اسمش پیداست میاد تاخیر زماندار میندازه یعنی میاد emit روی یک استریم رو بعد از زمان مشخصی اعمال میکنه.flatMap میاد هر emit روی استریم رو بصورت پشت سر هم اجرا میکنه یعنی تاخیری چیزی نداریم و اگر emit جدید بیاد هم اجرا میشهswitchMap هم میاد هر emit روی استریم که انجام میشه اگر هنوز کامل نشده پروسه emit شدن و یک emit جدید اومد اون قبلی رو متوقف میکنه و جدید رو میبره به مرحله emitMergeStream هم از اسمش پیداست میاد استریم هارو باهم ادغام میکنه و یک استریم به ما تحویل میدهبریم یه سرچ ساده با استفاده از Bloc و RxDart بسازیمبه کد زیر توجه کنید
class MyBlocEvents {}

class MyBlocSearch extends MyBlocEvents {
  final String query;
  MyBlocSearch({this.query = &quot;&quot;});
}

class MyBlocOtherEvent extends MyBlocEvents {
  final String extraData;
  MyBlocNew({this.extraData = &quot;&quot;});
}

class MyBloc extends Bloc&lt;MyBlocEvents, int&gt; {
  MyBloc(super.initialState) {
    on&lt;MyBlocEvents&gt;(
      (event, emit) async {
        if (event is MyBlocSearch) {
          print(&quot;Search Event&quot;);
        } else  {
          print(&quot;Other Event&quot;);
        }
      },
      transformer: (eventStream, mapperEventFunction) {
        final debouncedTimeStream = eventStream
            .where((event) =&gt; event is MyBlocSearch)
            .debounceTime(Duration(seconds: 3));
        final normalStream = eventStream.where((event) =&gt; event is! MyBlocSearch);
        return MergeStream([
          debouncedTimeStream.switchMap(mapperEventFunction),
          normalStream.flatMap(mapperEventFunction),
        ]);
      },
    );
  }
}
خب توی کد بالا ما یه event داریم که MyBlocEvent هست و دو ایونت دیگه که ارث بری شده از MyBlocEvent هست به نام های MyBlocSearch و MyBlocOtherEventیه bloc نوشتم که ایونت های MyBlocEvent رو قبول میکنه و مقدار emit اون عدد هست (int) توی بلاک اگر ایونت MyBlocSearch بود پرینت میکنه &quot;Search Event&quot; وگر نه پرینت میکنه &quot;Other Event&quot;برای اینکه بتونیم کاستومایز کنیم ایونت هامون رو میایم transformer رو کاستومایز میکنیم به این شکل که transformer ما یه فانکشن میگره که آرگیومنت های eventStram و mapperEventFunction هست eventStram استریمی از ایونت های ماmapperEventFunction فانکشنی که آرگیومنتش ایونت ما و خروجریش استریمی از ایونت ماست (که خیلی مهم نیست زیاد خودتو درگیر نکن)خروجی این transformer ما باید استریمی از نوع ایونت ما باشدخب میام اول چک میکنم که آیا ایونتی که پاس داده شده از نوع MyBlocSearch هست یا نه که اگر بود میگم که یه تاخیر سه ثانیه ای بزار با استفاده از debounceTime که این میشه اولین استریم تاخیر دار ماfinal debouncedTimeStream = eventStream
            .where((event) =&gt; event is MyBlocSearch)
            .debounceTime(Duration(seconds: 3));و میگم اگر از نوع MyBlocSearch نبود از نوع استریم نرماله و نیازی به تاخیر نیستfinal normalStream = eventStream.where((event) =&gt; event is! MyBlocSearch);حالا نیازه که یه ترکیب بزنیم که هردو نوع زمانی که Event اومد اجرا بشه که از MergeStream استفاده میکنیم و اینو بگم هنوز پایان کار نیست و باید نحوه اجرا شدن هر استریم رو هم بگیم return MergeStream([
          debouncedTimeStream.switchMap(mapperEventFunction),
          normalStream.flatMap(mapperEventFunction),
        ]);اینجا گفتم debouncedTimeStream رو به روش switchMap اجرا کن و اون فانکشن رو هم بهش دادم، حالا این روش چیکار میکنه، فرض کن که ایونتی ارسال کردی که از نوع MyBlocSearch هست خب میاد سه ثانیه صبر میکنه اگر قبل از سه ثانیه دوباره این ایونت رو ارسال کردی ایونت اولیت رو متوقف میکنه و این جدیده میاد رو کار حالا این برای کی خوبه زمانی که کاربر داره تایپ میکنه مثلا &quot;عدنان کمالی&quot; و bloc میخواد از سرور نتیجه رو بگیره با این حرکت که زدیم بعد از اینکه &quot;ع&quot; رو وارد کرد سه ثانیه منتظر میمونه اگر بعد از سه ثانیه حرفی وارد نکرد خب میره همین &quot;ع&quot; رو برات از سرور میاره و اگه ادامه داد مثلا شد &quot;عد&quot; قبل از سه ثانیه ایونت &quot;ع&quot; متوقف و ایونت &quot;عد&quot; شروع میشه و الی آخر.normalStram هم به روش flatMap که همون روش عادی هست.بریم ببینیم چه کردیمfinal myBloc = MyBloc(0);
  myBloc.add(MyBlocSearch(query: &quot;ع&quot;));
  await Future.delayed(Duration(seconds: 2));
  myBloc.add(MyBlocSearch(query: &quot;عد&quot;));
  await Future.delayed(Duration(seconds: 4));
  myBloc.add(MyBlocSearch(query: &quot;عدن&quot;));
  myBloc.add(MyBlocOtherEvent(extraData: &quot;Extra Data&quot;));توی این کد من بلاکم رو ساختم به اسم myBlocبهش ایونت MyBlocSearch زدم و بعد از دو ثانیه دوباره یه ایونت MyBlocSearch زدم، بعد از چهار ثانیه دوباره ایونت MyBlocSearch رو زدم و در آخر هم یه ایونت MyBlocOtherEvent زدم و خروجی شدSearch Event // بعد از پنج ثانیه
Other Event
Search Event // بعد از نه ثایهاولین خروجی بعد از یه دو ثانیه و سه ثانیه که برای تاخیر توی بلاک بود یعنی بعد از پنج ثانیه اجرا شد و کوئری &quot;عد&quot; بود چون بعدش چهار ثانیه delay داده بودیم و کوئری &quot;ع&quot; به دلیل کمتر از سه ثانیه بودن (چون ما دوثانیه delay داده بودیم متوقف شد)، بعدش MyBlocOtherEvent چون هیچ تاخیری نداشت اجرا شد و ایونت MyBlocSearch که کوئری &quot;عدن&quot; داشت توی تاخیر سه ثانیه بود و بعد از MyBlocOtherEvent اجرا شدامیدوارم که این نوشته براتون مفید بوده باشه منبع من برای این نوشته:pub.devعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 01 Aug 2025 21:06:08 +0330</pubDate>
            </item>
                    <item>
                <title>با Builder و Consumer پرفرمنس رو توی فلاتر بالاتر ببر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A8%D8%A7-builder-%D9%88-consumer-%D9%BE%D8%B1%D9%81%D8%B1%D9%85%D9%86%D8%B3-%D8%B1%D9%88-%D8%AA%D9%88%DB%8C-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-%D8%A8%D8%A7%D9%84%D8%A7%D8%AA%D8%B1-%D8%A8%D8%A8%D8%B1-bpyahyaphnjm</link>
                <description>پرفرمنس با بیلدر های استیت منجمنتاستیت منجمنت چیه؟بطور خلاصه برای اینکه بتونیم UI خودمون رو آپدیت کنیم و دیتای جدیدی رو توی صفحه به نمایش بذاریم نیازه که وضعیت رو مدیریت و ریفرش کنیم که برای مثال statefull با استفاده از setState که داره میاد کل اون ویجت رو ریفرش میکنه . مسئله همینجاست که کل ویجتو ریفرش میکنه و اگر یک صفحه داشته باشیم که با statefull نوشته شده باشه و بخوایم یک قسمت کوچیکی رو تغییر بدیم کل صفحه ریفرش میشه (شوخی: مثل این میمونه که دکتر بخواد پای بیمار رو عمل کنه ولی بیاد کل بدن رو بیهوش کنه :) )خب دکتر راه حل چیه؟راه حل برای این مورد اینه که فقط پای بیمار رو بیحسی بزنی یعنی اینکه اون قسمت رو فقط با statefull باز نویسی کنی و بقیه ویجت رو با stateless بزنی یکم راه حل رو بهتر کنیم و از builder ها استفاده کنیم؟معمولا ما از استیت منجمنت های مختلف استفاده می کنیم مثل provider که میاد یه بیلدری تحت عنوان Consumer در اختیار ما میذاره که هر زمانی که changenotifier رو کال کردیم فقط اون قسمت از ویجت ما که توی Consumer هست آپدیت میشه یا برای استیت منجمنت بلاک BlocBuilder داریم برای ری بیلدبدانید و آگاه باشید که stateless هم برای خودش بیلدر داره که اون هم هست StatefulBuilder که میاد فانکشن setState رو برامون فراهم میکنه و میتونیم توی اون یک تغییر استیت انجام بدیم که نمونش رو ببینیدawait showDialog&lt;void&gt;(
  context: context,
  builder: (BuildContext context) {
    int? selectedRadio = 0;
    return AlertDialog(
      content: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return Column(
            mainAxisSize: MainAxisSize.min,
            children: List&lt;Widget&gt;.generate(4, (int index) {
              return Radio&lt;int&gt;(
                value: index,
                groupValue: selectedRadio,
                d: (int? value) {
                  setState(() =&gt; selectedRadio = value);
                },
              );
            }),
          );
        },
      ),
    );
  },
);خیلی خب امیدوارم مطلب براتون مفید واقع شده باشه و حتما از بیلدر ها استفاده کنید عدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 18 Jul 2025 18:45:55 +0330</pubDate>
            </item>
                    <item>
                <title>استفاده از Dio Interceptor در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-dio-interceptor-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-og4vkhzjd0aj</link>
                <description>کار با dio interceptors در فلاترتوی پست قبلی درمورد Retrofit (رتروفت) در فلاتر صحبت کردیم که میاد کارمون رو توی درخواست های HTTP راحتتر میکنه حالا یکم با رهگیری درخواست ها توی دیو فلاتر آشنا بشیم تا بتونیم بهتر درخواست هامون رو رهگیری کنیمکار Dio Interceptor چیه اصلا؟این Interceptor یه نوع میدلور هم محسوب میشه که می تونیم روی درخواست ها، پاسخ ها و ارور ها قبل از اینکه هندل بشن دخالت کنیم برای مثال یه وقتایی هست که اپلیکیشن ما احراز هویت بیس هست یعنی تا کاربر ثبت نام نکنه و توکن به ما نده نمیتونه از برنامه استفاده کنه و حالا اگر بخوایم توکن هر دفعه که درخواست میدیم به هدر پاس بدیم توی پروژه های بزرگ خیلی جالب نیست (یا بهتره بگم به نوعی اصول DRY یا همون خودت رو تکرار نکن رو به نوعی رعایت نکردیم)خب راه حل این استفاده از رهگیر توی دیو هست که با دو مثال برات روشن میکنمچطوری یه Dio Interceptor بسازیمبرای ساخت رهگیر (Interceptor) کافیه از کلاس Interceptor ارث بری کنیم یا همون اکستند کنیم و متود های مورد نیاز مون که شامل موارد زیر هست اوراید (override) کنیمvoid onRequest(RequestOptions options, RequestInterceptorHandler handler)void onResponse(Response response, ResponseInterceptorHandler handler)Future (DioException err, ErrorInterceptorHandler handler)
import &#039;package:dio/dio.dart&#039;;
class CustomInterceptors extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print(&#039;REQUEST[${options.method}] =&gt; PATH: ${options.path}&#039;);
    super.onRequest(options, handler);
  }
  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print(&#039;RESPONSE[${response.statusCode}] =&gt; PATH: ${response.requestOptions.path}&#039;);
    super.onResponse(response, handler);
  }
  @override
  Future (DioException err, ErrorInterceptorHandler handler) async {
    print(&#039;ERROR[${err.response?.statusCode}] =&gt; PATH: ${err.requestOptions.path}&#039;);
    super.onError(err, handler);
  }
}برای مثال من توکن دارم به این توکن هست مثلا &quot;Adnan Kamali&quot; و میخوام هربار که دیو درخواستی ارسال میکنه این توکن توی هدر (Header) بره که برای این کار متود onRequest رو باز نویسی میکنم که میشه@override   
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {     
options.headers[&#039;Authorization&#039;] = &#039;Adnan Kamali&#039;; // &lt;- به هدر اضافه شد
super.onRequest(options, handler);
}یا مثلا یه وقتی هست که کاربر احراز هویت کرده ولی اکسس توکنش منقضی شده و ارور 401 میده که باید بریم از ریفرش توکن استفاده کنیم برای گرفتن اکسس توکن، که این هم توی متود  قابل هندل هست@override   
Future (DioException err, ErrorInterceptorHandler handler) async {     
if(err.response?.statusCode == 401){
   // درخواست بزن به سرور و اکسس رو بگیر
   // اکسس رو گرفتیم
  // دوباره اکسس رو توی هدر قرار میدیم و درخواست رو دوباره میفرستیم قبل از اینکه هندل کنیم ارور رو
  // یادت نره که اکسس رو ذخیره کنی که توی ریکوئست از اکسست دوباره استفاده کنی
  err.requestOptions.headers[&#039;Authorization&#039;] = &#039;Adnan Kamali&#039;;
  return handler.next(err);
}
super.onError(err, handler);  
 }خب اینجا چون معمولا از دیپندنسی اینجکشن برای ریپازیتوری هامون استفاده میکنیم میتونیم از همون Get_it برای گرفتن توکن ها در اینجا استفاده کنیم و به همین سادگی نیازی نیست دیگه هر دفعه توکن رو به عنوان پارامتر به ریپازیتوری ها بدیحالا چطوری از این رهگیر دیو استفاده کنمبرای اینکه بتونیم از این رهگیر استفاده کنیم کافیه بعد ساخت متود سازنده دیو (کانستراکتور) اون رو اضافه کنی به دیو یه نکته اینکه میتونی لیستی از رهگیر هارو بهش اضافه کنیfinal dio = Dio();
dio.interceptors.add(CustomInterceptors ());و کار تمومه حالا اگر از متغییر dio برای ارسال درخواست هات استفاده کنی اون رهگیره برات کار میکنههدف من از این نوشته آشنایی با Dio Interceptor هست که میتونید برای جزئیات بیشتر به وبسایتش که منبع استفاده من هست مراجعه کنید یعنی pub.devامیدوارم که لب مطلب رو رسونده باشم منبع من برای این نوشته pub.devAdnan Kamali | عدنان کمالی</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 23 May 2025 18:21:40 +0330</pubDate>
            </item>
                    <item>
                <title>کار با Retrofit در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%DA%A9%D8%A7%D8%B1-%D8%A8%D8%A7-retrofit-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-m9ofz87xepyg</link>
                <description>کار با Retrofit در فلاتراصلا Retrofit چی هست؟رتروفت یه کتابخونه ایه که به ما کمک میکنه ارتباط با شبکه که از نوع پروتکل HTTP هست یا به عبارت بهتر ارتباط با اند پوینت های rest رو برای ما آسون تر و تمیز تر میکنهروش نصب رتروفت در فلاتربرای اینکه از رتروفت در فلاتر استفاده کنیم نیازه که چند پکیج رو به همراه اون نصب داشته باشیم پس برای نصب هر کدوم به روش زیر عمل میکنیمflutter pub add retrofit logger json_annotation dev:retrofit_generator dev:build_runner dev:json_serializableیه نکته اینکه رتروفت از دیو برای ارسال درخواست ها استفاده میکنه اگر دیدی بهت ارور داد dio رو هم نصب کنخب اولین پکیج که خود رتروفت هست و دومی هم لاگر برای لاگ زدن رتروفت در صورت بروز ارور و اینا و سومی که جیسون انوتیشن برای انوتیشن گذاری استفاده میشه (همون که اولش اتساین(@) داره)رتروفت جنریتور که توی دیپندنسی دولوپ هست برای تولید کد های رتروفت و بیلد راننر هم برای اجرای دستور بیلد و همچنین جیسون سریالازبل برای سریالایز کردن مدل های ما هستتعریف و تولید کد ای پی ای های مابرای تعریف ای پی ای هامون مثل مثال زیر عمل میکنیمimport &#039;package:dio/dio.dart&#039;;
import &#039;package:json_annotation/json_annotation.dart&#039;;
import &#039;package:retrofit/retrofit.dart&#039;;

part &#039;example.g.dart&#039;;

@RestApi(baseUrl: &#039;https://MyApi.com/v1&#039;)
abstract class RestClient {
  factory RestClient(Dio dio, {String? baseUrl}) = _RestClient;
  @GET(&#039;/tasks&#039;)
  Future&lt;List&lt;Task&gt;&gt; getTasks();
}این نکته رو بگم که اگر فقط این کد رو کپی پیست کنی احتمالا به ارور هایی مثل تعریف نشده بخورید و این به این دلیل هست که هنوز کد اصلی ما تولید نشده ما فرض می کنیم که فایل ما به نام example.dart هست بعد از ایمپورت های اصلی باید به دارت(یا همون بیلد راننر) بگیم که آقا این فایل من که example.dart هست قراره بخشی از اون رو توی فایل example.g.dart جنریت بشه پس کد  &#x27;part &#x27;example.g.dart رو اضافه میکنیم در مرحله بعد یه انوتیشن داریم تحت RestApi@ که این انوتیشن بهش پارامتر بیس یو آرل میدیم که این پایه url ما هست برای مثال ما آدرس های زیر رو داریمhttps://MyApi.com/v1/tasks/https://MyApi.com/v1/task/https://MyApi.com/v1/delete/task/{id}/خب این سه تا یو آر ال بیس ما میشه https://MyApi.com/v1 و در ادامه تغییر میکنهپس توی انوتیشن RestApi@ آدرس پایه مون رو میدیمیک ابسترکت کلاس می نویسیم به همراه اسمی که میخوایم که اسمش گذاشتیم RestClient خب بعد توی یک فکتوری بهش دیو و آدرس پایه رو تعریف کردیم و مساوی آندرلاین اسم کلاس خب حالا وقت تعریف کردن متود هامون رسیده که حالات مختلف داره برای مثال برای درخواست GET از آدرس /https://MyApi.com/v1/tasks چون آدرس پایه رو بهش دادیم کافیه انتهایی که میخوایم رو بهش بدیم که میشهGET(&#x27;/tasks/&#x27;)@ حالا کلاسمون که قراره چی بر بگردونه از نوع فیوچر مینویسیم که اینجا قراره لیستی از مدل Task بر بگردونهاین مدل ما حتما باید جی سون سریالایزبل باشه یا متود fromJson تعریف شده باشه برای مدل ماخب تعریف انجام شد بریم تا کد رو جنریت کنیمچون رتروفت معمولا یه فرمت خاص داره برای تعریف کلاس بهتره برای اینکه سریع تر بنویسی و کد رو کپی پیست نکنی از Mason برای تولید کد تکراری استفاده کنیبرای جنریت کردن کد رتروفت کافیه دستور زیر رو توی ترمینال پروژه اجرا کنیمdart run build_runner buildو بعد از پایان کار تمومه میتونی درخواست رو بزنی یکمی عمیق تر توی رتروفتخب ما که فقط نیاز به یه درخواست ساده GET تنها نداریم ممکنه درخواست ما دیتا داشته باشه و یا حتی Header و شایدم داینامیک باشه لینک ما یعنی یه آیدی بخوایم وسط لینک بذاریم و بعد درخواست رو بدیم اضافه کردن دیتا به درخواست یا همون body کافیه که اونجا که فیوچر رو تعریف میکنیم انوتیشن Body ضافه کنیم که میشه به شکل زیر@POST(&amp;quot/task/&amp;quot)
Future&lt;Task&gt; postTask(@Body() Map&lt;String, dynamic&gt; data);حالا اگه بخوایم heder هم اضافه کنیم هم مثل بادی عمل میکنیم و انوتیشن Header میذاریم و هدری که میخوایم ارسال بشه مثلا میخوام Content-Type بفرستم@POST(&amp;quot/task/&amp;quot) 
Future&lt;Task&gt; postTask(@Header(&#039;Content-Type&#039;) String contentType, @Body() Map&lt;String, dynamic&gt; data);اگر دیدی نمیشه بجای ایمپورت dio اینو ایمپورت کنimport &#039;package:dio/dio.dart&#039; hide Headers;برای  اینکه بتونیم داینامیک لینک داشته باشیم هم داینامیک لینک رو به شکل زیر تعریف میکنیم@DELETE(&#039;delete/task/{id}/&#039;)
Future&lt;void&gt; deleteTask(@Path(&#039;id&#039;) String taskId);در اینجا درخواست دیلیت با آیدی که توی آدرس تعریف کردیم میرهخب حالا چطوری از این رتروفت استفاده کنیمبرای استفاده از رتروفت کافیه کلاس آبسترکتی که تعریف کردیم رو بسازیم و دیو رو بهش پاس بدیم و متود هارو صدا بزنیمfinal dio = Dio();
final myRestClinet = RestClient(dio);
try{
   final tasks = await myRestClient.getTasks();
}on DioException catch(e){
   // TODO: Catch error
}توجه کنید که اگر پاسخ متود getTasks ساکسس باشد یعنی 2xx باشد بدنه try اجرا میشود ولی اگر ساکسس نباشد یعنی 400 به بالا اون موقع خطا از نوع DioException میده و هندلش باید کردامیدوارم این پست براتون مفید شده باشه و از استفاده از رتروفت لذت ببریدمنابع من برای این پست pub.devعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 16 May 2025 18:59:06 +0330</pubDate>
            </item>
                    <item>
                <title>نوشتن داکیومنت موثر برای کد ها در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D9%86%D9%88%D8%B4%D8%AA%D9%86-%D8%AF%D8%A7%DA%A9%DB%8C%D9%88%D9%85%D9%86%D8%AA-%D9%85%D9%88%D8%AB%D8%B1-%D8%A8%D8%B1%D8%A7%DB%8C-%DA%A9%D8%AF-%D9%87%D8%A7-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-kq1pevmfi1fb</link>
                <description>نوشتن کامنت موثر در فلاتر دارتامروز که کد می نویسیم آسونه که به کد نگاهی بکنیم و بگیم آره!! این کدیه که کار میکنه ولی بعد ها افراد دیگری قراره روی کد ما کار کنن و توسعه بدن، یا حتی خود ما بعد از مدتی (شاید سال ها بعد) بیایم روی کدی که نوشتیم بر بگردیم و اوووچ!! این دیگه برای چی نوشتم اینجا و الی آخرنوشتن یک کامنت مختصر و دقیق ممکنه فقط چند ثانیه طول بکشه ولی همین کامنت، میتونه ساعت ها در وقت یک برنامه نویس دیگه یا حتی خود ما صرفه جویی بکنهانواع کامنت در دارتما سه نوع کامت گذاری در دارت داریمنوع تک خطی که با دوتا اسلش نمایش میدن ( Comment //)نوع چند خطی که با اسل ستاره شروع و با ستاره اسلش خاتمه پیدا میکنه (/* Comment */)نوع سوم که بهش داکیومنتیشن کامنت هست که برای نوشتن داکیومنت از اون استفاده میشه و به دو صورت هست که با سه اسلش ( Comment ///) و اسلش سه تا ستاره اسلش (/* Comment **/)نوشتن داکیومنت برای کدقبل از هر چیزی باید بگم که باید کد ها کلا خوانا باشه در نوشتار ولی جایی هست که برای درک بهتر کامنت گذاری می کنیمبرای نوشتن کامنت مثل جمله عمل میکنیم توی زبان لاتین که با کپتال (حرف اول باید بزرگ نوشته بشه) شروع میشه و برای نوشتن یک توضیح کوتاه برای یک خط کد معمولا از کامنت تک خطی استفاده میکنیم مثلاvoid greet(String name) {  
 // Assume we have a valid name.   
 print(&#039;Hi, $name!&#039;); 
} یه نکه اینکه از کامنت چند خطی معمولا برای کامنت کردن بخشی از کد استفاده میشه نه برای توضیح حالا اگر بخوایم برای کلاس یا یک فایل داکیومنت بنویسیم از سه اسلش استفاده میکنیم و توضیحات رو مینویسم مثلاclass RadioButtonWidget extends Widget {
  /// Sets the tooltip to [lines].
  ///
  /// The lines should be word wrapped using the current font.
  void tooltip(List&lt;String&gt; lines) {
    ...
  }
}نکته اول اینکه ما توی کامنت های داکیومنت میتونیم رفرنس بدیم که مثلا با محیط برنامه نویسی وی اس کد با نگه داشتن دکمه کنترل کیبرد و کلیک روی اون رفرنس به محل رفرنس برید که با براکت ([ ]) رفرنس مون رو میدیم مثلا توی این مثال [lines] که توی کامنت هست رفنرس میده پارامتر tooltipنکته دوم، زمانی که اسم کلاس مثلا خیلی واضحه دیگه نیازی به کامنت اضافه نیست، حالا چرا؟ چون ممکنه باعث سردرگمی بشه توی این مثال اسم کلاس مشخصه که چه ویجتی هست نکته سوم، حد امکان کامنت داکیومنت خوانا و کم باشه بهترهنکته چهارم و آخر اینکه اگر چند جمله ای هست داک کامنتتون اون رو مثل مثال بالا جدا کنید یعنی جمله اول اگر تمام شد یه بریک لاین خالی هم با کامنت بذاریداستخراج داکیومنت هایی که نوشتیم بصورت یکجااگر با اصول ساخت نرم افزار آشنا باشید یه اصلی هست که میگه باید داکیومنت داشته باشی که حالا بصورت فیلم میتونی کد هاتو توضیح بدی یا بنویسی و ...خب ما نیاز داریم که داکیومنت کدهامون بصورت یکجا به دست برنامه نویس بعدی یا سایرین باشه که بتونن از داکیومنت استفاده کنند که برای استخراج اینها دارت خودش یه کامندی داره با اجرای اون دستور کامنت ها استخراج میشن که اون دستور این هستdart doc .این دستور میگه که برای کد های من داکیومنت هاش رو توی پوشه روت جنریت کن و میاد داکیومنت های کد مارو بصورت یکجا در دسترس قرار میدهحالا اگر بخوایم بصورت لوکال در دسترس باشه و روی وب ببینیم کافیه دستورات زیر رو اجرا کنیمdart pub global activate dhttpd
dart pub global run dhttpd --path doc/apiحالا داکیومنت های ما با آدرس http://localhost:8080در دسترس استامیدوارم مطالب به درتون خورده باشهمنابع من برای این نوشته:Effective Dart: Documentation | Dartdart doc | DartComments | DartAdnan Kamali | عدنان کمالی</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 09 May 2025 17:53:12 +0330</pubDate>
            </item>
                    <item>
                <title>بلاک کانکارنسی (bloc concurrency) در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A8%D9%84%D8%A7%DA%A9-%DA%A9%D8%A7%D9%86%DA%A9%D8%A7%D8%B1%D9%86%D8%B3%DB%8C-bloc-concurrency-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-egydhddcdnyx</link>
                <description>بلاک کانکارنسی در فلاتردر این نوشته قراره که درمورد بلاک کانکارنسی یا بهتره بگم استفاده از بلاک کانکارنسی در ترنسفرمر بلاک هستو یاد خواهید گرفت که نحوه انتقال ایونت ها در بلاگ چگونه باشد و امیت چگونه انجام بشهاول از همه باید بگم برای نصب پکیج بلاک کانکارنسی در روت پروژه دستور زیر رو وارد کنید تا براتون نصب بشهقبل از نصب یادتون باشه که بلاک رو نصب داشته باشیدflutter pub add bloc_concurrencyانواع ایونت ترنسفرمر (transformers)ما چهار نوع ترنسفرمر یا همون انتقال دهنده توی بلاک کانکارنسی داریم که عبارت از1- concurrent2- sequential3- droppable4- restartableترنسفرمر concurrentاین ترنسفرمر که بلاک هم بصورت دیفالت از این استفاده میکنه. در این روش هر ایونتی که بسمت بلاک بفرستیم اونو بصورت همزمان انجام میده و براش مهم نیست که ایونت دیگری هم آیا هنوز در کار هست یا نه من از مثال سرچ برای تمامی کیس ها استفاده میکنم مثلا ما یک باکس سرچ داریم و به ازای هر کلمه که توی باکس تایپ میکنیم یک ایونت از تکست که توی باکس هست به سمت بلاک ما میاد و در منطق بلاک اینطوره که با اومدن ایونت میره و به سرور درخواست اون کلمه رو میزنه و نتیجه رو به کاربر امیت(emit) میکنهخب در این نوع ترنسفرمر دیگه براش مهم نیست که آیا اون ایونت که به سمت بلاک رفت امیت شده یا نشده بلاک میاد پروسه گرفتن مقدار سرچ رو دوباره شروع میکنهکه این مورد پیشنهاد نمیشه برای سرچ البتهترنسفرمر sequentialاین ترنسفرمر بصورت پله ای اوینت هارو امیت میکنه یعنی ایونت هارو جمع میکنه و زمانی که یک ایونت امیت شد میره و بعدی رو میخونه و امیت می کنهبرای سرچ مثلا من توی باکس سرچ می نویسم &quot;عدنان کمالی چاهوئی&quot; حالا بلاک اول حرف اول اسم منو میاد از سرور میگیره و نتیجه رو به کاربر نمایش میده بعد میره حرف اول و دوم رو از سرور میگیره و به کاربر نشون میده و الی ...که باز هم این مورد توی سرچ به کار نمیاد ترنسفرمر droppableاین ترنسفرمر میاد اولین ایونت رو میگیره و هرچی که همزمان با انجام این ایونت باشه رو در نظر نمیگیرهبرای مثال توی سرچ من اگر &quot;عدنان کمالی چاهوئی&quot; رو سرچ کنم پشت سر هم، اولین ایونت که اول اسم من باشه یعنی حرف &quot;ع&quot; درخواست به سرور ارسال میشه و تازمانی که این حرف &quot;ع&quot; امیت نشده باشه بقیه ایونت ها در نظر نمیگیره مثلا تا من سرچ بکنم &quot;عدنان کمالی &quot; حرف &quot;ع&quot; امیت شده و در ادامه که می نویسم &quot;چاهوئی&quot; دو باره این حرف &quot;چ&quot; رو شروع میکنم به نوشتن ایونت &quot;عدنان کمالی چ&quot; میره برای دریافت از سرورکه به این روش روش پرشی هست و به درد سرچ نمیخورهترنسفرمر restartableاین ترنسفرمر میاد که کارو انجام بده میبینه یه اینونت جدید اومد، کار الانشو ول میکنه میره سراغ ایونت جدید توی سرچ مثلا من اگر تایپ بکنم توی سرچ باکس &quot;عدنان کمالی چاهوئی&quot; تا مادامی که من توی سرچ مینویسم و امیت نشده کار فعلی رو رها میکنه و درواقع امیت انجام نمیشهاین روش بهترین گزینه برای سرچ هست که البته میشه با rxdart ادغام کرد و سرچ های بهینه تری انجام داد و البته روش های دیگری هم هست.برای درک بهتر این عکس رو میتونید مشاهده کنیدانواع ترنسفرمر در بلاکاستفاده از ترنسفرمر هاخب حالا چطوری این ترنسفرمر ها توی بلاک استفاده کنیمیه نکته رو بگم اینکه این ترنسفرمر ها فقط برای بلاک هست و برای کیوبیت همچین قابلیتی نداریمimport &#039;package:bloc/bloc.dart&#039;;
import &#039;package:bloc_concurrency/bloc_concurrency.dart&#039;;

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}

class CounterBloc extends Bloc&lt;CounterEvent, int&gt; {
  CounterBloc() : super(0) {
    on&lt;CounterIncrementPressed&gt;(
      (event, emit) async {
        await Future.delayed(Duration(seconds: 1));
        emit(state + 1);
      },
      transformer: sequential(), // اینجا ترنسرفر هارو استفاده کن
    );
  }
}امیدوارم مطلب به دردتون خورده باشه منبع من برای این نوشته:pub.devعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Fri, 02 May 2025 17:50:43 +0330</pubDate>
            </item>
                    <item>
                <title>بلاک هایدریت (hydrated bloc) در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A8%D9%84%D8%A7%DA%A9-%D9%87%D8%A7%DB%8C%D8%AF%D8%B1%DB%8C%D8%AA-hydrated-bloc-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-vqfaf9fm934r</link>
                <description>مدیریت وضعیت با بلاک هایدریتبلاک هایدریت اصلا کارش چیه؟همینطور که میدونید مدیریت وضعیت در برنامه نویسی فرانت کلا بخش جدانشدنی هست حالا چه اپلیکیشن باشه چه فرانت وبسایت و ما در فلاتر مدیریت وضعیتی به نام بلاک دارم که از محبوبیت بسیار زیادی برخورداره و معمولا در پروژه های بزرگ یکی از گزینه های روی میز هست.در کل بلاک هایدریت میاد یه اپشن اضافه رو بهمون میده که اون هم ذخیره اصلاعات و وضعیت رو به ما میده که توی نوشته من یک نمونه رو بهتون نشون میدم که مربوط به تم هاست که خیلی راهت بتونید تم رو ذخیره کنید و وضعیت آخرین تم رو به نمایش بگذارید که همون دارک و لایت هست.یه نکته ای رو بگم که این بلاک هایدریت از hive_ce برای ذخیره اطلاعات استفاده میکنهنصب و کانفیگ اولیه بلاک هایدریتبرای نصب بلاک هایدریت نیازه بلاک هم نصب بشه، کافیه کد زیر رو بزنید توی روت پروژه (مثل بقیه پکیج ها که نصب میکند)flutter pub add hydrated_bloc
flutter pub add flutter_blocبعد از نصب نیازه که کافیگ اولیه رو انجام بدیم که این کافیگ قبل از ران اپلیکیشن هست و در تابع اصلی (main) و این کانفیگ هم مربوط میشه به hive_ce که در پایین مشاهده می کنیدیه نکته اینکه ما برای ذخیره اطلاعات نیاز به یک دایرکتوری دارم پس برای گرفتن آدرس محل ذخیره از path_provider استفاده میکنیم و از تابعgetTemporaryDirectoryبرای دریافت مسیرflutter pub add path_providerFuture&lt;void&gt; main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorageDirectory.web
        : HydratedStorageDirectory((await getTemporaryDirectory()).path),
  );
  runApp(App());
}حالا کانفیگ اولیه بلاک هایدریت ما انجام شده بریم برای ساخت استیت منجمنت تم با بلاک هایدریتساخت مدیریت وضعیت تم با بلاک هایدریتخب بریم که کدش رو بزنیمclass ThemeBloc extends HydratedBloc&lt;ThemeEvent, ThemeState&gt; {
        ThemeBloc() : super(ThemeModeState(themeMode: ThemeMode.system)) {
        on&lt;ThemeEvent&gt;((event, emit) {
        switch (event) {
               case LightMode():
                   emit(ThemeModeState(themeMode: ThemeMode.light));
                   break;
              case DarkMode():
                   emit(ThemeModeState(themeMode: ThemeMode.dark));
                   break;
             case SystemMode():
                   emit(ThemeModeState(themeMode: ThemeMode.system));
                   break;
             }
          });
       }
   @override
    ThemeState? fromJson(Map&lt;String, dynamic&gt; json) {
    switch (json[&amp;quotthemeIndex&amp;quot]) {
         case 1:
              return ThemeModeState(themeMode: ThemeMode.light);
         case 2:
              return ThemeModeState(themeMode: ThemeMode.dark);
         default:
              return ThemeModeState(themeMode: ThemeMode.system);
       }
    }
    @override
    Map&lt;String, dynamic&gt;? toJson(ThemeState state) {
        switch (state) {
             case ThemeModeState(themeMode: ThemeMode.light):
                 return {&amp;quotthemeIndex&amp;quot: 1};
             case ThemeModeState(themeMode: ThemeMode.dark):
                 return {&amp;quotthemeIndex&amp;quot: 2};
            default:
                 return {&amp;quotthemeIndex&amp;quot: 0};
            }
       }
}



// States
@immutable
sealed class ThemeState {
final ThemeMode themeMode;
const ThemeState({required this.themeMode});
}
final class ThemeModeState extends ThemeState {
const ThemeModeState({required super.themeMode});
}


// Events
@immutable
sealed class ThemeEvent {}
final class DarkMode extends ThemeEvent {}
final class LightMode extends ThemeEvent {}
final class SystemMode extends ThemeEvent {}همینطور که میبینید من از هایدریت بلاک استفاده کردم و در ادامه استست ها و اینونت هارو میبینید و نکته مهمی که هست:هایدریت بلاک از دو متود مهم یعنی fromJson و toJson استفاده میکنه که toJson که میبینید یک مپ یا جیسون بر میگردونه که این برگشته توی دیتابیس محلی ذخیره میشه و fromJson زمانی که بلاک اینیشیالایز شد این متود میاد و مقدار آخرین وضعیت که سیو شده رو پس میدهو اینکه هر بار که ما ایونتی میفرستیم و امیت میشه، متود toJson صدا زده میشه و استیت ما در اون ذخیره میشهو بقیه کار هم مثل بلاک هست اون رو پرواید میکنیم و از .add برای ارسال ایونت استفاده میکنیم که کدش رو میتونید ببینیدFuture&lt;void&gt; main() async {
     WidgetsFlutterBinding.ensureInitialized();
     HydratedBloc.storage = await HydratedStorage.build(
     storageDirectory:
      kIsWeb
     ? HydratedStorageDirectory.web
     : HydratedStorageDirectory((await getTemporaryDirectory()).path),
    );
    runApp(BlocProvider(create: (context) =&gt; ThemeBloc(), child: const MyApp()));
}
class MyApp extends StatelessWidget {
   const MyApp({super.key});
   @override
    Widget build(BuildContext context) {
       return BlocBuilder&lt;ThemeBloc, ThemeState&gt;(
       builder: (context, state) {
           return MaterialApp(
            debugShowCheckedModeBanner: false,
              title: &#039;Flutter Theme&#039;,
             themeMode: state.themeMode,
            theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(
             seedColor: Colors.deepPurple,
       ),
    ),
           darkTheme: ThemeData(
         colorScheme: ColorScheme.fromSeed(
         seedColor: Colors.deepPurple,
        brightness: Brightness.dark,
     ),
  ),
    home: const ThemeManagerWidget(),
    );
   },
  );
  }
  }
class ThemeManagerWidget extends StatelessWidget {
const ThemeManagerWidget({super.key});
@override
Widget build(BuildContext context) {
      final themeBloc = context.read&lt;ThemeBloc&gt;();
     return Scaffold(
     appBar: AppBar(
       backgroundColor: Theme.of(context).colorScheme.inversePrimary,
       title: const Text(&#039;Theme Management&#039;),
        actions: [
          IconButton(
              onPressed: () =&gt; themeBloc.add(SystemMode()),
              icon: Icon(Icons.monitor),
          ),
          IconButton(
            onPressed: () =&gt; themeBloc.add(LightMode()),
            icon: Icon(Icons.light_mode),
          ),
          IconButton(
            onPressed: () =&gt; themeBloc.add(DarkMode()),
            icon: Icon(Icons.dark_mode),
          ),
       ],
),
   body: Center(child: Text(&amp;quottheme&amp;quot)),
);
}
}توی اپ بار سه دکمه برای تغییر تم گذاشتم و روی هر دکمه که کلیک کنید تم عوض میشه و حتی اگر اپ رو ببندید و بربگردید آخرین تم که انتخاب کردید به شما نمایش داده میشهامیدوارم مطلب رو به جا اورده باشم منابع من برای این نوشته:pub.devعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Sat, 26 Apr 2025 14:24:27 +0330</pubDate>
            </item>
                    <item>
                <title>سازگاری و اجرای فلاتر با آخرین نسخه گریدل</title>
                <link>https://virgool.io/@adnankamali1248/%D8%B3%D8%A7%D8%B2%DA%AF%D8%A7%D8%B1%DB%8C-%D9%88-%D8%A7%D8%AC%D8%B1%D8%A7%DB%8C-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-%D8%A8%D8%A7-%D8%A2%D8%AE%D8%B1%DB%8C%D9%86-%D9%86%D8%B3%D8%AE%D9%87-%DA%AF%D8%B1%DB%8C%D8%AF%D9%84-wuwvzt6tozmk</link>
                <description>سازگاری گریدل با فلاتراصلا گریدل (Gradle) چیکار میکنه؟بطور خلاصه و مفید کارش بیلد گرفتنه و تبدیل کد های ما به یک برنامه قابل اجرا  مثل APK که حالا این گریدل میاد فایل های build.gradle که توی ورژن کاتلینی و جدیدا فلاتر هم ورژن کاتلینی استفاده میکنه به نام build.gradle.kts هست، حالا گفته میشه (گوگل میگه) که برای خوانایی و برسی زمانی کامپایل بهتر و همچنین محیط های برنامه نویسی بهتر ساپورت میکنن یه نکته رو هم بگم اگر حوصله خوندن نداری یه لینک یوتیوب پایین گذاشتم میتونی از روی فیلم بری جلوبرای دانلود به گریدل حتما نیاز به فیلترشکن خواهید داشتاز build.gradle به build.gradle.ktsبعضی از پروژه های فلاتری قدیمی هستن که از build.gradle و settings.gradle استفاده میکنن و حالا میخوان که با ورژن جدید فلاتر برنامه شون رو اجرا و بیلد بگیرن که این تجربه خودمم بوده.هنگام اجرا به یکسری خطا ها میخورن که یه همچین خطایی هست &quot;Deprecated imperative apply of Flutter Gradle plugins | Flutter&quot;که رفرنس میده به سایت داکیومنت فلاتر که میگه آقاجان build.gradle دیپریکیت شده بیا و آپدیت بشو به ktsآپدیت کردن build.gradle به build.gradle.ktsخب در android/build.gradle یه همچین چیزی داریمbuildscript {
    ext.kotlin_version = &#039;1.7.10&#039;
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath &#039;com.android.tools.build:gradle:7.3.0&#039;
        classpath &amp;quotorg.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version&amp;quot
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.buildDir = &#039;../build&#039;
subprojects {
    project.buildDir = &amp;quot${rootProject.buildDir}/${project.name}&amp;quot
}
subprojects {
    project.evaluationDependsOn(&#039;:app&#039;)
}

tasks.register(&amp;quotclean&amp;quot, Delete) {
    delete rootProject.buildDir
}یه فایل build.gradle.kts در پوشه اندروید بسازید و توش اینو بنویسیدallprojects {
    repositories {
        google()
        mavenCentral()
    }
}

val newBuildDir: Directory = rootProject.layout.buildDirectory.dir(&amp;quot../../build&amp;quot).get()
rootProject.layout.buildDirectory.value(newBuildDir)

subprojects {
    val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
    project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
    project.evaluationDependsOn(&amp;quot:app&amp;quot)
}

tasks.register&lt;Delete&gt;(&amp;quotclean&amp;quot) {
    delete(rootProject.layout.buildDirectory)
}خب تا اینجا build.gradle.kts مون ساخته شده بریم برای settings.gradle.ktsحالا نوبت به settings.gradle رسیده که معمولا توش مقادیر زیر هستpluginManagement {
      def flutterSdkPath = {
             def properties = new Properties()
              file&#40;&amp;quotlocal.properties&amp;quot&#41;.withInputStream { properties.load(it) }
              def flutterSdkPath = properties.getProperty(&amp;quotflutter.sdk&amp;quot)
              assert flutterSdkPath != null, &amp;quotflutter.sdk not set in local.properties&amp;quot
              return flutterSdkPath
        }
        settings.ext.flutterSdkPath = flutterSdkPath()
        includeBuild(&amp;quot${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle&amp;quot)
        plugins {
        id &amp;quotdev.flutter.flutter-gradle-plugin&amp;quot version &amp;quot1.0.0&amp;quot apply false
        }
}
include &amp;quot:app&amp;quot

apply from: &amp;quot${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle&amp;quotحالا یک فایل settings.gradle.kts  رو در پوشه اندروید ایجاد کرده و مقادیر زیر رو اضافه میکنیمpluginManagement {
    val flutterSdkPath = run {
        val properties = java.util.Properties()
        file&#40;&amp;quotlocal.properties&amp;quot&#41;.inputStream().use { properties.load(it) }
        val flutterSdkPath = properties.getProperty(&amp;quotflutter.sdk&amp;quot)
        require(flutterSdkPath != null) { &amp;quotflutter.sdk not set in local.properties&amp;quot }
        flutterSdkPath
    }

    includeBuild(&amp;quot$flutterSdkPath/packages/flutter_tools/gradle&amp;quot)

    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id(&amp;quotdev.flutter.flutter-plugin-loader&amp;quot) version &amp;quot1.0.0&amp;quot
    id(&amp;quotcom.android.application&amp;quot) version &amp;quot8.9.1&amp;quot apply false
    id(&amp;quotorg.jetbrains.kotlin.android&amp;quot) version &amp;quot2.1.20&amp;quot apply false
}

include(&amp;quot:app&amp;quot)نکات قابل توجه توی قسمت plugins ما سه تا پلاگین دارم که با اولی کاری نداریماما دوتای دیگه که مربوط به پلاگین گریدل هست (توجه کنید که اندروید پلاگین گریدل یا AGP) که آخرین نسخه ریلیز شده در تاریخی که من دارم اینو مینویسم &quot;8.9.1&quot; هست و میتونید ورژن هاش رو توی  وبسایت اندروید دولوپر  ببنید و آخرین نسخه رو قرار بدید و درمورد پلاگین کاتلین هم آخرین ورژنی که درحال حاضر هست &quot;2.1.20&quot; و برای مشاهده آخرین ورژن به وبسایت کاتلین بخش ریلیز آخرین ورژن رو قرار بدیدحالا می رسیم به آپدیت کردن android/app/build.gradle به android/app/build.gradle.ktsتوی android/app/build.gradle یه همچین چیزی هست
plugins {
    id &amp;quotcom.android.application&amp;quot
    id &amp;quotkotlin-android&amp;quot
    id &amp;quotdev.flutter.flutter-gradle-plugin&amp;quot
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file&#40;&#039;local.properties&#039;&#41;
if (localPropertiesFile.exists()) {
     localPropertiesFile.withReader(&#039;UTF-8&#039;) { reader -&gt;
     localProperties.load(reader)
      }
}
.... // یه سری چیزا
android { 
      namespace &amp;quotcom.example.[project]&amp;quot
       compileSdkVersion 34
        ndkVersion flutter.ndkVersion
        compileOptions {
                 sourceCompatibility JavaVersion.VERSION_1_8
                  targetCompatibility JavaVersion.VERSION_1_8
         }
         kotlinOptions {
          jvmTarget = JavaVersion.VERSION_1_8
          }
          sourceSets {
          main.java.srcDirs += &#039;src/main/kotlin&#039;
           }
            defaultConfig {

            applicationId &amp;quotcom.example.[project]&amp;quot
            minSdkVersion 21
            targetSdkVersion 34
            versionCode flutterVersionCode.toInteger()
            versionName flutterVersionName
            multiDexEnabled true
             }
             signingConfigs {
                       release {
                             ...
                       }
                 }
                 buildTypes {
                         release {
                            ...
                         }
                }
}
flutter {
         source &#039;../..&#039;
}dependencies {}
وقشته فایل build.gradle.kts خودمون رو توی پوشه اندروید/اپ بسازیم و مقادیر زیر رو وارد کنیمplugins {
    id(&amp;quotcom.android.application&amp;quot)
    id(&amp;quotkotlin-android&amp;quot)
    id(&amp;quotdev.flutter.flutter-gradle-plugin&amp;quot)
}

android {
    namespace = &amp;quotcom.example.[project]&amp;quot
    compileSdk = flutter.compileSdkVersion
    ndkVersion = &amp;quot29.0.13113456&amp;quot

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_23
        targetCompatibility = JavaVersion.VERSION_23
        isCoreLibraryDesugaringEnabled = true
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_23.toString()
    }

    defaultConfig {
        applicationId = &amp;quotcom.example.[project]&amp;quot
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutter.versionCode
        versionName = flutter.versionName
        multiDexEnabled = true
    }

    buildTypes {
        release {
            ...
        }
    }
}
dependencies {
...
}
flutter {
    source = &amp;quot../..&amp;quot
}نکاتش رو هم بگماولین نکته اینه که اندروید استودیو بصورت پیش فرض از جاوا ورژن 21 استفاده میکنه در زمان نوشتن من. و آخرین ورژنی که وجود داره 24 هست، خب من اومدم با ورژن 24 امتحان کنم دیدم که با کاتلین ورژن آخر کانفیلیکت میخوره (یا به عبارتی به ارور برخوردم) اومدم و از ورژن قبل تر یعنی 23 استفاده کردم که در ادامه روش نصب و ست کردن JDK رو در ادامه میگم خب برای ست کردن ورژن 23 مقادیر که در بالا مشاهده میکنید رو ست میکنیم در کامپایل آپشنsourceCompatibility = JavaVersion.VERSION_23   
targetCompatibility = JavaVersion.VERSION_23و jvmTargetjvmTarget = JavaVersion.VERSION_23.toString()خب بریم که ورژن 23 جاوا رو نصب کنیم از سایت اوراکل میایم جاوای 23 رو که نسخه exe یا msi رو دانلود میکنم و نصب لوکیشن پیشفرض C:\Program Files\Java\jdk-23 هست و همین رو با کد زیر به فلاتر میفهمونیم که آقا جاوا اینجا هست و از این جاوا استفاده کنflutter config --jdk-dir &amp;quotC:\Program Files\Java\jdk-23&amp;quotو برای اینکه اثر کنه میگه که یکبار ترمینال یا سی ام دی رو ببند و دوباره باز کنو تا اینجا کار تقریبا 90 درصد کار رفته و فقط میمونه استفاده از آخرین ورژن گریدلبروزرسانی گریدل به آخرین نسخهبرای اینکه از نسخه آخر گریدل استفاده کنیم نیازه یه سر به وبسایت گریدل بزنیم که برای من درحال حاضر آخرین نسخه گریدل 8.13 هست و ورژن رو کافیه در فایل gradle-wrapper.properties عوض کنیم یعنی این خط کد رو عوض کنیمdistributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zipفقط ورژن رو تغییر بدید یعنی میتونید توی کد بالا بجای 8.13 بنویسید مثلا 8.14 و آخرین نکته اینکه میتونید سازگاری ورژن کاتلین و جاوا رو از وبسایت گریدل ببینید و متناسب با آن ورژن های دیگه رو هم تطابق بدیدامیدوارم تونسته باشم مشکل گریدل رو برای فلاتری ها حل کرده باشممنابع من برای این نوشتهفیلم حل مشکل گریدل در فلاتر توسط کامیونیتی فلاتر فارسیdeveloper.androiddocs.flutter.devdoc.gradle.orgعدنان کمالی | Adnan Kamali</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Sat, 19 Apr 2025 19:52:51 +0330</pubDate>
            </item>
                    <item>
                <title>تولید کد تکراری با Mason در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D8%AA%D9%88%D9%84%DB%8C%D8%AF-%DA%A9%D8%AF-%D8%AA%DA%A9%D8%B1%D8%A7%D8%B1%DB%8C-%D8%A8%D8%A7-mason-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-t4bmzpbyhpn8</link>
                <description>mason در فلاتریه وقتایی هست توی فلاتر که هی یک کد رو تکرار میکنیم یا درواقع ساختار تکراری کد رو کپی، پیست میکنیمخب چرا نیایم اینو اتوماتیک کنیممسون یا mason چی هست؟در واقع با مسون (mason) ما میایم یکسری متغییر هارو تعریف می کنیم و متناسب با متغییر ها تمپلیت پایه رو تعریف میکنیم که با فراخوانی مسون و دادن مقدار متغییر برای ما کد رو جنریت میکنه مثلا برای استفاده از پکیج json_serializable یه ساختار مشابهی رو داریم که به شکل زیر هست import &#039;package:json_annotation/json_annotation.dart&#039;;

part &#039;example.g.dart&#039;;

@JsonSerializable()
class Example{
  Example({});


  factory Example.fromJson(Map&lt;String, dynamic&gt; json) =&gt; _$ExampleFromJson(json);
  Map&lt;String, dynamic&gt; toJson() =&gt; _$ExampleToJson(this);
}همین طور که مشاهده می کنید یک ساختار مشابه برای تمامی این مدل صادق است و تنها اسم آن که در این مثال Example است تغییر می کند و متغییر های خود کلاس که دست خودمونهخب برای اینکه این ساختار و تمپلیت رو جنریت کنیم هی نیایم کپی کنیم حالا حذف کنیم و فقط با چند دستور تمپلیت با نام دلخواه ما ساخته بشه از مسون استفاده می کنیمنصب و کار با Masonبرای نصب و فعال سازی mason کد زیر رو وارد ترمینال میکنیمdart pub global activate mason_cliدر مرحله بعدی باید در پوشه روت پروژه یا همون پوشه ای که pubspec.yaml داره میریم و دستور زیر را وارد می کنیم تا mason برای ما مقدار دهی بشهmason initحالا بعد از اجرا دستور یک فایل تحت عنوان mason.yaml برای ما ایجاد میشه که در اون mason هایی که میسازیم رو اضافه می کنیمساخت bricksبرای ساخت brick کافیه دستور زیر رو اجرا کنیم (example نام brick شما خواهد بود)mason add exampleبعد از اون باید این brick رو به mason.yaml اضافه کنیم به شکل زیر bricks:  example:      path: example # example  در پوشه روت قرار دارهو با دستور زیر brick خودمون رو به مسون میشناسونیمmason getحالا وقت ساخت ساختار تمپلیت خودمونهساخت ساختار brickبعد از ساخت brick که با اسم example ساختیم یک پوشه به نام example توی روت پروژه اضافه میشه که دارای __brick__ پوشهbrick.yaml این دوتا برای ما اهمیت داره که در پوشه __brick__ ساختار تعریف می کنیم و در brick.yaml متغییر های و سایر اطلاعات که متغییر name بصورت دیفالت تعریف شدهخب بریم که ساختارمون رو بسازیمدر پوشه __brick__  دقیقا مکانی که قراره فایل قرار بگیره مشابه سازی میکنیم مثلا من میخوام در پوشه lib یک فایل با یک نامی بسازم که اسنیک کیس باشه و درونش هم یک سریالایزر باشه، پس میام در __brick__ یک پوشه lib میسازم و فایلم رو به اسم {{name.snakeCase()}}.dart که به شکل زیر کد نویسی میکنمimport &#039;package:json_annotation/json_annotation.dart&#039;;part &#039;{{name.snakeCase()}}_model.g.dart&#039;;@JsonSerializable()class {{name.pascalCase()}} {{{name.pascalCase()}}({});factory {{name.pascalCase()}}.fromJson(Map&lt;String, dynamic&gt; json) =&gt;_${{name.pascalCase()}}FromJson(json);// model to json objectMap&lt;String, dynamic&gt; toJson() =&gt; _${{name.pascalCase()}}ToJson(this);}خب حالا با دستور زیر را اجرا میکنیم mason make exampleو اسم متغییر name رو بهش میدیم مثلا من اسم زیر رو بهش میدم که my_example هست? What is your name? (Dash) my_example
و بعله توی پوشه lib ما فایل تحت عنوان my_example داریم که محتواش کد زیر هستimport &#039;package:json_annotation/json_annotation.dart&#039;;part &#039;my_example_model.g.dart&#039;;@JsonSerializable()class MyExampleModel {MyExampleModel({});// json object to modelfactory MyExampleModel.fromJson(Map&lt;String, dynamic&gt; json) =&gt;_$MyExampleModelFromJson(json);// model to json objectMap&lt;String, dynamic&gt; toJson() =&gt; _$MyExampleModelToJson(this);}حالا کافیه متغییر هاتو وارد کنی و از بیلد راننر استفاده کنیاین نکته رو هم بگم که من یک مثال ساده زدم و البته که میتونید کار ها و جنریتور های پیشرفته تر هم بنویسد.منبع استفاده شده در این مقاله وبسایت pub.dev</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Wed, 09 Apr 2025 18:37:07 +0330</pubDate>
            </item>
                    <item>
                <title>استریم (Stream) در دارت</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A7%D8%B3%D8%AA%D8%B1%DB%8C%D9%85-stream-%D8%AF%D8%B1-%D8%AF%D8%A7%D8%B1%D8%AA-llq5okbzduli</link>
                <description>Stream In Dartدرباره برنامه نویسی ناهمزمان با استفاده از Future صحبت کردیم که چطوری از فیوچر استفاده کنیم تا بصورت ناهمزمان دو فعالیت رو در دارت انجام بدیم و گفتیم که در دارت ناهمزمانی با دو تایپ انجام میشه که یکی Future بود و یکی هم که قراره دربارش صحبت کنیم Stream هست پس اگر اون بخش رو نخوندی حتما پیشنهاد میکنم بری بخونی که اینجا به کارت میاد.استریم ها اصلا چی هستن؟استریم ها درواقع یک جریان از دیتا هستن که بصورت ناهمزمان کار میکنن.اگر بخوام زیر دیپلم و برای کسی بگم که تازه وارده بهتره بگم همون لیست خودمونه که حالا یکسری قابلیت هایی بهش دادن و بهش میگن ناهمزمان.درواقع همون فانکشن هایی که روی لیست داشتیم رو اومده فقط تایپ خروجیش رو بصورت فیوچر کرده و یکم ریزه کاری داره که در ادامه تو کد میبینیم.چگونه یک استریم درست کنیم؟برای ساخت استریم راه های متعددی وجود داره یکی از روش ها با استفاده از فانکشن که استریم بر میگردونهبرای مثالStream&lt;int&gt; countStream(int to) async* {  for (int i = 1; i &lt;= to; i++) {    yield i;  }}در دو نکته قابل توجه وجود دارهقبل از بدنه فانکشن کلید واژه *async استفاده شده که برای استریم ها هستخروجی رو بجای return از yield استفاده کردیمشرح کد بالا اینه که این فانکشن رو بهش هر عددی بدیم یه جریانی(لیست) از یک تا اون عدد رو به ما برمیگردونه، حالا من میام اینو فراخوانی میکنمFuture&lt;void&gt; main() async {  var stream = countStream(10);  final firstNumberInStream = await stream.first; // 1}در اینجا اومدم اون استریم خودم رو فراخوانی کردم و حالا جریانی از اعداد از یک تا 10 رو دارم میام و اولیش رو در میارم با که با استفاده از stream.first که فیوچر بهم میده ولی اومدم از await استفاده کردم که داده اصلی رو بهم بربگردونه همون عدد یک هست رو بربگردونهحالا چرا گفتم مثل لیست عمل میکنه این استریم؟متود های زیر رو میتونید ببینید که روی استریم قابل استفاده هست و من از اولیش توی مثال بالا استفاده کردم.خودتون میتونید ببینید که تقریبا متود هاش با متود های لیست توی دارت برابری میکنه ولی چون خروجی بصورت Future هست پس یه ناهمزمانی توش رخ دادهFuture&lt;T&gt; get first;Future&lt;bool&gt; get isEmpty;Future&lt;T&gt; get last;Future&lt;int&gt; get length;Future&lt;T&gt; get single;Future&lt;bool&gt; any(bool Function(T element) test);Future&lt;bool&gt; contains(Object? needle);Future&lt;E&gt; drain&lt;E&gt;([E? futureValue]);Future&lt;T&gt; elementAt(int index);Future&lt;bool&gt; every(bool Function(T element) test);Future&lt;T&gt; firstWhere(bool Function(T element) test, {T Function()? orElse});Future&lt;S&gt; fold&lt;S&gt;(S initialValue, S Function(S previous, T element) combine);Future forEach(void Function(T element) action);Future&lt;String&gt; join([String separator = &#x27;&#x27;]);Future&lt;T&gt; lastWhere(bool Function(T element) test, {T Function()? orElse});Future pipe(StreamConsumer&lt;T&gt; streamConsumer);Future&lt;T&gt; reduce(T Function(T previous, T element) combine);Future&lt;T&gt; singleWhere(bool Function(T element) test, {T Function()? orElse});Future&lt;List&lt;T&gt;&gt; toList();Future&lt;Set&lt;T&gt;&gt; toSet();متود های تعدیل کننده استریمیکسری متود ها هستن که روی استریم اگر بزنیم استریم جدید با کمی تعدیل یا تغییر به ما بر میگردونه که تقریبا هم مثل لیست ها توی دارت هست که این متود ها عبارت از Stream&lt;R&gt; cast&lt;R&gt;();Stream&lt;S&gt; expand&lt;S&gt;(Iterable&lt;S&gt; Function(T element) convert);Stream&lt;S&gt; map&lt;S&gt;(S Function(T event) convert);Stream&lt;T&gt; skip(int count);Stream&lt;T&gt; skipWhile(bool Function(T element) test);Stream&lt;T&gt; take(int count);Stream&lt;T&gt; takeWhile(bool Function(T element) test);Stream&lt;T&gt; where(bool Function(T event) test);استفاده از متود listenما گفتیم که استریم ها یک جریان هستن، حالا اینجاست که جریان بودن خودش رو به رخ میکشه.فرض کنید که شما درحال پیام دادن به یک دوست خوب هستید، و دوست شما برای اینکه پیام هارو دریافت کنه باید تلفن همراهش روشن باشه که پیام ها یا لیستی از پیام های شما به اوشون برسه، پس دوست شما یک جریانی(استریم) رو داره. حالا اینجا شرکت مخابرات میاد و بین صحبت های شما و دوستتون میشینه و به پیام ها گوش میده به عبارتی listen میکنه و شما با هربار ارسال پیام (yield) میاد و پیام شمارو گوش میده.خب بریم که پیاده سازیش کنیم.حالا توی این مثال فرض کنید شما پیام رو هر سه ثانیه از یک تا هشت رو برای دوستتون می فرستیدStream&lt;int&gt; sendMessage()async*{    for(int i = 1;i &lt;= 8;i++){        await Future.delayed(Duration(seconds:3)); // سه ثانیه تاخیر        yield i;    }}void main(){   final stream = sendMessage();   stream.listen((value)=&gt;print(value));   print(&quot;َAsync&quot;);   print(&quot;Stream&quot;);}توی اینجا استریم فرستنده پیام به نام sendMessage رو فراخوانی کردم که هر سه ثانیه بیاد عدد رو از یک تا هشت بفرسته توی جریان یا همون استریم که لیست خودمون میشه و بعد توی فانکشن اصلی (main) اومدم استریم رو فراخوانی کردم و سر راهش یک گوش دهنده (listen) قرار دادم و گفتم بهش هر وقت پیامی رو ارسال کرد یا همون yield کرد بیا گوش بده ببین چیه و برام پرینت کنبرای اینکه نا همزمانی رو نشون بدم اومدم دوتا پرینت هم بعدش گذاشتم که ببینید خروجی بصورت نا همزمان هستو در نهایت خروجی بعد از 24 ثانیه:َAsyncStream12345678امیدوارم تونسته باشم استریم هارو بصورت ساده توضیح داده باشم و مطلب براتون مفید واقع شده باشه.منبع من برای این مطلب  وبسایت pub.dev هست با کمی تغییر.</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Sun, 02 Mar 2025 18:54:39 +0330</pubDate>
            </item>
                    <item>
                <title>برنامه نویسی ناهمزمان در دارت</title>
                <link>https://virgool.io/@adnankamali1248/%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D9%86%D8%A7%D9%87%D9%85%D8%B2%D9%85%D8%A7%D9%86-%D8%AF%D8%B1-%D8%AF%D8%A7%D8%B1%D8%AA-nkro539vxkek</link>
                <description>برنامه نویسی ناهمزمان در فلاترتوی فلاتر یه زمان هایی هست که میخوایم دیتایی رو از سرور دریافت کنیم یا دیتایی رو توی دیتابیس بنویسیم یا حتی دیتایی رو از فایلی بخونیم اگر بصورت همزمان یا Synchronous انجام بدیم باعث میشه که برنامه ما فریز بشه و تا زمانی که این عملیات انجام نشده کاربر ما هر حرکتی روی اپلیکیشن بزنه انگار نه انگار برنامه کار میکنه و اگر این عملیات طول بکشه کاربر حتی فکر میکنه که برنامه ما هنگ کرده و فوش و الی آخربرنامه نویسی ناهمزمان کلید موفقیتبرای اینکه بتونیم از دعوا و مرافعه دوری کنیم میایم از برنامه نویسی ناهمزمان یا Asynchronous  استفاده میکنیم، حالا چطوری؟برای استفاده از برنامه نویسی ناهمزمان در دارت ما از این کلمات کلیدی استفاده میکنیم:FutureStreamasync (or async* for Stream)await yield (or yield*) use in Streamنتیجه های محاسبات ناهمزمان معمولا بصورت Future است یعنی خروجی Future به ما می دهد و اگر نتیجه های ما  چند بخشی باشد مثل صفحه بندی (Pagination) که باید درخواست صفحه بعد داد رو با Stream پیاده سازی میکنیمیه نکته رو بگم ما در این بخش از Future استفاده میکنیم و در آینده برای Stream نوشته خواهم داشتچیه Futureیک Future در واقع نشان دهنده عملیات ناهمزمانه که دو حالت براش هست تکمیل نشده    در این وضعیت زمانی که شما یک فانکنش ناهمزمان (Future) را فراخوانی می کنید، همون موقع یک Future تکمیل نشده به شما بر میگردونه که درحال پروسه انجام، حالا ممکنه این پروسه تکمیل بشه یا ارور برگشت داده بشه2. تکمیل شده در این وضعیت عملیات موفقیت آمیز بوده و Future ما تکمیل شده به همراه مقدار و یا ممکنه تکمیل شده همراه با اروراین فیوچر ها میتونن مقدار بگیرن مثل String یا هر تایپی که تعریف کرده باشیم که به شکل &lt;Future&lt;T که T همون تایپ مقدار ما هستن که برای رشته میشه &lt;Future&lt;String برای مثالFuture&lt;void&gt; fetchUserOrder() {  // فکر کنید این تابع از دیتابیس دیتایی رو میگیره و به مدت 2 ثانیه طول میکشه.  return Future.delayed(const Duration(seconds: 2), () =&gt; print(&#x27;Got it From Database&#x27;));}void main() {  fetchUserOrder();  print(&#x27;Fetching user order from database...&#x27;);}ما از زمانی که  فانکشن ناهمزمان خودمون رو یعنی fetchUserOrder رو فراخوانی میکنیم بعد از دو ثانیه تاخیر که بصورت فیک با Future.delayed بهش دادیم مقدار Got it From Database رو برامون پرینت میکنه و براش فرقی نداره که چه کد هایی بعدش هست.خب پس خروجی نهایی کد میشه Fetching user order from database...Got it From Database // مشاهده بعد از دو ثانیهاستفاده از async, awaitکلمات کلیدی async  و await برای ما این امکان رو فراهم میکنه که توی فانکشن های ناهمزمان استفاده کنیم و از مقدار نهایی آنها بهره مند شیم که دوتا نکته دارهاستفاده از async قبل از بدنه فانکشن ناهمزمان استفاده میشهکلمه کلیدی await باید در فانکشنی استفاده بشه که دارای async است و گرنه بدون اون کار نمیکنهبرای اینکار در مرحله اول فانکشن main رو تبدیل به فیوچر میکنیم  و کلمه کلیدی async رو قبل از بدنه اضافهو در کد زیر فانکشن countSecond میاد بصورت ناهمزمان ثانیه هارو میشماره که تا 6 ثانیه هستفانکشن getOrderMessage داریم که با چهار ثانیه تاخیر مقدار &quot;Order message&quot; بر میگردونهیه نکته رو بگم که کلید  واژه await به معنی صبر کردن هست  و این کلمه کلیدی میاد بعد از صبر مقدار رو برمیگردونهخب میایم میگیم که برای فانکشن getOrderMessage صبر کن تا مقدار واقعیش رو بربگردونه و بعد مقدار رو پرینت کنحالا اگر کد رو اجرا کنیم ... Future&lt;void&gt; main() async {countSeconds(6);final result = await getOrderMessage();print(result);}Future&lt;String&gt; getOrderMessage() async {await Future.delayed(Duration(seconds: 4));return &quot;Order message&quot;;}void countSeconds(int s) {for (var i = 1; i &lt;= s; i++) {Future.delayed(Duration(seconds: i), () =&gt; print(i));}}نتیجه رو به شکل زیر میبینیم که بعد از ثانیه 4 مقدار Order message چاپ میشهresult:1234Order message56امیدوارم تونسته باشم مفهوم فیوچر رو جا انداخته باشممنبع من برای این نوشته  وبسایت pub.dev هست</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Wed, 26 Feb 2025 12:08:14 +0330</pubDate>
            </item>
                    <item>
                <title>ویجت به ارث رسیده (Inherited Widget) در فلاتر</title>
                <link>https://virgool.io/@adnankamali1248/%D9%88%DB%8C%D8%AC%D8%AA-%D8%A8%D9%87-%D8%A7%D8%B1%D8%AB-%D8%B1%D8%B3%DB%8C%D8%AF%D9%87-inherited-widget-%D8%AF%D8%B1-%D9%81%D9%84%D8%A7%D8%AA%D8%B1-eluf62baytq8</link>
                <description>Inherited Widget Diagramحتما شنیدین که میگن این مال از پدرش به ارث رسیده تو زندگیش خیلی راحته و سختی نکشیده.حالا فلاتر هم برای اینکه دیتایی از ویجت پدر به ویجت فرزند برسه اومده کار رو راحت تر کرده که دیگه به زحمت نیوفتیم.خود من در اوایل استفاده از فلاتر میومدم برای اینکه به یک ویجت فرزند دیتایی رو پاس بدم برای هر کدون یک کانستراکتور تعریف می کردم حالا باید برای هر کدوم از زیر ویجت هام که فراخوانی می کردم این دیتا رو پاس می دادم یا حتی بدتر ممکن بود توی یک ویجت اصلا کاربرد نداشته باشه دیتا ولی چون یک ویجت فرزند داشت که به اون دیتا نیاز داشت باید پاس میدادی مثل کد زیر:دیتا من در ParentWidget هست ولی FirstChildWidget نیاز دارهاستفاده از  مدیریت وضعیت (State manager)بعد از گذشت اولین تجربه و آشنا شدن با مدیریت وضعیت ها (استیت منجر) شرایط یکم تغییر کرد حالا دیگه خیالم راحت بود که پارامتری نیاز نیست پاس بدم و میام توی این مدیریت وضعیت متغییرم رو تعریف می کنم و دیتا رو بدون کانستراکتور ویجت ها و بدون پل زدن از ویجت میانی می فرستم به ویجتی که میخوام، اینم خوب بود ولی خب ممکن بود این استیت منجر من یکم اضافه بار داشته باشه خب ما نمیایم که یک استیت منجیر بسازیم که یک متغییری رو پاس بدیم حتما توی استیت منجر مون تابع هایی وجود داره و الی آخر که این آبجکت رو اگر بخوایم اضافه کنیم به بخشی از کد به کل اونها دستری خواهیم داشت.استفاده از استیت منیجر برای دیتاارث بردن و راحت شدن (InheritedWidget)این کلاس خود فلاتره که به نوعی یک ویجت محسوب میشه و قالبیتی که داره میتونه توی کانتکست زیر مجموعه خودش رخنه کنه و به عنوان پدر دارایی هاش رو با استفاده از کانتکست به بقیه فرزنداش به اشتراک بگذاره قبل از دیدن کد یه چندتا از الزامات رو بذار بگمبا توجه به اینکه پدر فرزند هست پس حتما باید فرزند داشته باشه پس child باید داشته باشهمتود updateShouldNotify که مقدار بولین بر میگردونه میگه کی باید آپدیت بشم که معمولا اینجوریه که اگر مقدار دیتا تغییر نکرد از ویجت قبلیت نیاز به آپدیت نیست و ری بیلد نیازی نیست بشنرایجه که از of استفاده کنیم و توی اون برسی کنیم که آیا این کانتکست ما اون ویجت به ارث برده داره یا نه اگر نداره ارور نشون بده تا توی حالت دیباگ بدونیم که از کلاسمون در جای درست استفاده کردیم یا نهAccess Data Using Inherited Widgetفقط یک نکته رو بگم که اگر میخوای مستقیم توی خود اون کلاس دیتا رو استفاده کنی لازمه از بیلدر کمک بگیری.Direct Useامیدوارم مطلب مفید واقع شده باشه.</description>
                <category>Adnan Kamali | عدنان کمالی</category>
                <author>Adnan Kamali | عدنان کمالی</author>
                <pubDate>Sat, 22 Feb 2025 17:39:26 +0330</pubDate>
            </item>
            </channel>
</rss>