فلاتر

آموزش پیاده سازی Bloc 1.0 در فلاتر

تقریبا بعد از گذشت یک سال از توسعه پکیج bloc بالاخره نسخه پایدار 1.0 این کتابخانه منتشر شد که شامل تغییرات زیادی نسبت به نسخه های قبل تر از آن می باشد.

در این مطلب قصد داریم با همدیگه نگاهی به تغییرات نسخه جدید بلاک و همچنین پیاده سازی یک مثل ساده فلاتر با ورژن جدید کتابخونه Bloc و تمرین کنیم.

تبدیل Bloc ها به Stream

در نسخه جدید کتابخونه بلاک تمام Bloc ها در واقع Stream هستند. به این معنی هست که دیگه نیازی به نوشتن Stream<State> get state نداریم.

از این به بعد به طور مستقیم میتونید به تغییرات state بلاک گوش دهید.

bloc.listen((state({
}):

Bloc به عنوان Sink

در ورژن 1.0 کتابخونه بلاک تمام Bloc ها به عنوان Sink نیز میتوانند عمل کنند.

این پیاده سازی یعنی از کلمه کلیدی Add که در دستورات Sink قرار داشت نیز میتوانید از این به بعد برای بلاک ها هم استفاده کنید.

در نسخه جدید Bloc کلمه dispatch حذف شده و برای اضافه کردن ایونت جدیدی باید از کلمه add استفاده کنید.

bloc.add(Event());

تغییر دیگر جایگزین شدن دستور dispose با دستور close می باشد.

برای درک بهتر و تمرین پروژه عملی کوچیکی و با همدیگه انجام میدیم.

بعد از اینکه پروژه جدیدی ایجاد کردید کتابخونه های زیر را به برنامه اضافه کنید.

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^1.0.0
  equatable: ^0.6.1

از کتابخونه Equatable برای مقایسه آبجکت های مختلف استفاده می کنیم.

پروژه ای که میسازیم کار نمایش اطلاعات آب و هوایی و انجام میده اما بصورت آفلاین و با اطلاعات نمایشی.

یک کلاس به نام Weather ایجاد میکنیم که از کلاس Equatable ارث بری خواهد کرد.

class Weather extends Equatable {
  final String cityName;
  final double temperatureCelsius;
  final double temperatureFarenheit;

  Weather({
    @required this.cityName,
    @required this.temperatureCelsius,
    this.temperatureFarenheit,
  });

  @override
  List get props => [
        cityName,
        temperatureCelsius,
        temperatureFarenheit,
      ];
}

بخش get props به دلیل استفاده از کتابخونه Equatable هست و ما مشخص میکنیم این متد چه فیلدهایی و قرار هست برگردونه.

آموزش فلاتر

برای اینکه کدهای تمیز تری داشته باشیم کلاس جدیدی به نام WeatherRepository ایجاد میکنیم که منبع دریافت اطلاعات را مشخص می کند.

بلاک با این کلاس سر و کار دارد و این کلاس تعیین میکند که اطلاعات از کجا دریافت شوند برای مثال از اینترنت یا دیتابیس داخلی گوشی.

abstract class WeatherRepository {
  Future fetchWeather(String cityName);
  Future fetchDetailedWeather(String cityName);
}

گفتیم که قرار هست اطلاعاتی که در این پروژه نمایش میدهیم واقعی نباشه. پس یک کلاس برای ایجاد دیتا های ساختگی میسازیم.

 class FakeWeatherRepository implements WeatherRepository {
  double cachedTempCelsius;

  @override
  Future fetchWeather(String cityName) {
    // Simulate network delay
    return Future.delayed(
      Duration(seconds: 1),
      () {
        final random = Random();

        // Simulate some network error
        if (random.nextBool()) {
          throw NetworkError();
        }

        // Since we're inside a fake repository, we need to cache the temperature
        // in order to have the same one returned for the detailed weather
        cachedTempCelsius = 20 + random.nextInt(15) + random.nextDouble();

        // Return "fetched" weather
        return Weather(
          cityName: cityName,
          // Temperature between 20 and 35.99
          temperatureCelsius: cachedTempCelsius,
        );
      },
    );
  }

  @override
  Future fetchDetailedWeather(String cityName) {
    return Future.delayed(
      Duration(seconds: 1),
      () {
        return Weather(
          cityName: cityName,
          temperatureCelsius: cachedTempCelsius,
          temperatureFarenheit: cachedTempCelsius * 1.8 + 32,
        );
      },
    );
  }
}

class NetworkError extends Error {}

در مورد الگوی Bloc بلاک در فلاتر قبلا صحبت کردیم و برای آشنا شدن با مفاهیم اولیه بهتر هست که به آن مراجعه کنید.

قبل از پیاده سازی بخش منطقی برنامه باید درباره نیازمندی ها و اکشن های برنامه فکر کنید تا بتونید بخش Event ها را تکمیل کنید.

برای مثال در این پروژه با رویدادهای مختلفی مثل دریافت وضعیت کلی هوا, دریافت جزییات وضعیت هوا و… سر و کار داریم.

import 'package:equatable/equatable.dart';

abstract class WeatherEvent extends Equatable {
  const WeatherEvent();
}

class GetWeather extends WeatherEvent {
  final String cityName;

  const GetWeather(this.cityName);

  @override
  List get props =&gt; [
        cityName
      ];
}
}

class GetDetailedWeather extends WeatherEvent {
  final String cityName;
  const GetDetailedWeather(this.cityName);
 
  @override
  List get props =&gt; [
        cityName
      ];
}}

برای پیاده سازی بخش State اگر میخواید که زیاد دچار مشکل نشوید بهتره که از خودتون سوال کنید “در چند حالت مختلف State میتونه رابط کاربری و تغییر بده؟”

در این پروژه ما میخوایم کاربر با وارد کردن نام شهر بعد از نمایش یک لودینگ وضعیت هوا را در برنامه نمایش دهیم.

همچنین در هنگام نمایش اطلاعات ممکن هست که به خطا یا ارور هم برخورد کنیم.

  • نام شهر
  • لودینگ
  • وضعیت هوا
  • نمایش خطا

پس تقریبا 4 وضعیت مختلف State داریم.

abstract class WeatherState extends Equatable {
  const WeatherState();
}

class WeatherInitial extends WeatherState {
  const WeatherInitial();
  @override
  List get props => [];
}

class WeatherLoading extends WeatherState {
  const WeatherLoading();
  @override
  List get props => [];
}

class WeatherLoaded extends WeatherState {
  final Weather weather;
  const WeatherLoaded(this.weather);
  @override
  List get props => [weather];
}

class WeatherError extends WeatherState {
  final String message;
  const WeatherError(this.message);
  @override
  List get props => [message];
}


هر کلاس Bloc باید دو بخش مهم و حتما پیاده سازی کند که شامل initialState و متد mapEventToState است.

متد mapEventToState قبلا شامل دو پارامتر بود که در بروزرسانی جدید پارامتر currentState حذف شده اما با دستور state خالی میتونید به آن دسترسی داشته باشید.

class WeatherBloc extends Bloc {
  final WeatherRepository repository;

  WeatherBloc(this.repository);

  @override
  WeatherState get initialState => WeatherInitial();

  @override
  Stream mapEventToState(
    WeatherEvent event,
  ) async* {
    // Emitting a state from the asynchronous generator
    yield WeatherLoading();
    // Branching the executed logic by checking the event type
    if (event is GetWeather) {
      // Emit either Loaded or Error
      try {
        final weather = await repository.fetchWeather(event.cityName);
        yield WeatherLoaded(weather);
      } on NetworkError {
        yield WeatherError("Couldn't fetch weather. Is the device online?");
      }
    } else if (event is GetDetailedWeather) {
      // Code duplication ? to keep the code simple for the tutorial...
      try {
        final weather = await repository.fetchDetailedWeather(event.cityName);
        yield WeatherLoaded(weather);
      } on NetworkError {
        yield WeatherError("Couldn't fetch weather. Is the device online?");
      }
    }
  }
}

برای طراحی رابط کاربری برنامه از ویجت BlocProvider استفاده میکنیم تا بر ساس State های مختلف ویجت های متفاوت و نمایش دهیم.

 
void main() =&gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Weather App',
      home: BlocProvider(
        builder: (context) =&gt; WeatherBloc(FakeWeatherRepository()),
        child: WeatherSearchPage(),
      ),
    );
  }
}


 
class WeatherSearchPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Weather Search"),
      ),
      body: Container(
        padding: EdgeInsets.symmetric(vertical: 16),
        alignment: Alignment.center,
        child: BlocBuilder<weatherbloc, weatherstate="">(
          builder: (context, state) {
            if (state is WeatherInitial) {
              return buildInitialInput();
            } else if (state is WeatherLoading) {
              return buildLoading();
            } else if (state is WeatherLoaded) {
              return buildColumnWithData(context, state.weather);
            } else if (state is WeatherError) {
              return buildInitialInput();
            }
          },
        ),
      ),
    );
  }


برای اینکه با هر بار تغییر اطلاعات بتونیم یک اسنکبار نمایش دهیم از BlocListener به شکل زیر استفاده میکنیم.

 child: BlocListener<weatherbloc, weatherstate="">(
  listener: (context, state) {
    if (state is WeatherError) {
      Scaffold.of(context).showSnackBar(
        SnackBar(
          content: Text(state.message),
        ),
      );
    }
  },
  child: BlocBuilder<weatherbloc, weatherstate="">(
    builder: (context, state) {
      ...
    },
  ),
),



کلاس CityInputField یک ویجت اختصاصی هست که متدی به نام submitCityName دارد. این متد یک پارامتر به نام cityName دریافت و یک Event جدید ثبت میکند.

 
class CityInputField extends StatelessWidget {
  ...
  void submitCityName(BuildContext context, String cityName) {
    // Get the Bloc using the BlocProvider
    // False positive lint warning, safe to ignore until it gets fixed...
    final weatherBloc = BlocProvider.of<weatherbloc>(context);
    // Initiate getting the weather
    weatherBloc.add(GetWeather(cityName));
  }
}


قصد داریم که با کلیک روی دکمه وارد صفحه جدیدی به نام WeatherDetailPage بشیم و اطلاعات آب و هوا را نمایش دهیم.

 
RaisedButton(
  child: Text('See Details'),
  color: Colors.lightBlue[100],
  onPressed: () {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (_) =&gt; BlocProvider.value(
        value: BlocProvider.of<weatherbloc>(context),
        child: WeatherDetailPage(
          masterWeather: weather,
        ),
      ),
    ));
  },
),

برای اینکار از BlocProvider.value استفاده میکنیم اما باید توجه داشته باشید حتما در صفحه قبلی باید builder پیاده سازی شده باشد.

Hesam

View Comments

Recent Posts

گیتهاب اکشن چیست؟ آموزش استفاده از گیتهاب اکشن در برنامه نویسی فلاتر

گیتهاب اکشن GitHub Actions یکی از ابزارهای گیتهاب است که به شما کمک می‌کنه تا…

1 هفته ago

آموزش افزایش سرعت اجرای وب اپلیکیشن های فلاتر

اگر یک برنامه نویس فلاتر هستید و با از نسخه وب اپلیکیشن پروژتون استفاده میکنید…

1 ماه ago

آموزش جامع انتشار اپلیکیشن اندروید و فلاتر در فروشگاه گوگل پلی Google play

به عنوان یک برنامه نویس فلاتر یا اندروید بعد از اتمام پروسه طراحی اپلیکیشن نیاز…

2 ماه ago

دانلود سورس کد رابط کاربری اپلیکیشن فلاتر پروژه پادکست

طراحی رابط کاربری اپلیکیشن پادکست خود را با استفاده از این کیت توسعه UI/UX فلاتر…

3 ماه ago

فایربیس چیست؟ معرفی سرویس ابری Firebase و کاربردهای آن

فایربیس، پلتفرمی قدرتمند از شرکت گوگل برای توسعه و مدیریت برنامه‌های موبایل و وب است.…

3 ماه ago

آموزش پیاده سازی Method Channel در فلاتر + فیلم

فلاتر یک فریم ورک برنامه نویسی چندسکویی است که این امکان را برای برنامه نویس…

3 ماه ago