Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(weather_example): simplify state management and improve layout #4216

Merged
merged 5 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

27 changes: 1 addition & 26 deletions docs/src/content/docs/tutorials/flutter-weather.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weath
import RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';
import WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';
import WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';
import ThemeCubitTreeSnippet from '~/components/tutorials/flutter-weather/ThemeCubitTreeSnippet.astro';

![advanced](https://img.shields.io/badge/level-advanced-red.svg)

Expand Down Expand Up @@ -516,23 +515,6 @@ Remember to generate the (de)serialization code via:
<BuildRunnerBuildSnippet />
:::

### Theme

Next, we'll implement the business logic for the dynamic theming.

#### Theme Cubit

Let's create a `ThemeCubit` to manage the theme of our app. The theme will change based on the current weather conditions.

<ThemeCubitTreeSnippet />

We will expose an `updateTheme` method to update the theme depending on the weather condition.

<RemoteCode
url="https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/theme/cubit/theme_cubit.dart"
title="lib/theme/cubit/theme_cubit.dart"
/>

### Unit Tests

Similar to the data and repository layers, it's critical to unit test the business logic layer to ensure that the feature-level logic behaves as we expect. We will be relying on the [bloc_test](https://pub.dev/packages/bloc_test) in addition to `mocktail` and `test`.
Expand All @@ -548,13 +530,6 @@ Let's add the `test`, `bloc_test`, and `mocktail` packages to the `dev_dependenc
The [bloc_test](https://pub.dev/packages/bloc_test) package allows us to easily prepare our blocs for testing, handle state changes, and check results in a consistent way.
:::

#### Theme Cubit Tests

<RemoteCode
url="https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/theme/cubit/theme_cubit_test.dart"
title="test/theme/cubit/theme_cubit_test.dart"
/>

#### Weather Cubit Tests

<RemoteCode
Expand Down Expand Up @@ -651,7 +626,7 @@ Our `main.dart` file should initialize our `WeatherApp` and `BlocObserver` (for
title="lib/main.dart"
/>

Our `app.dart` widget will handle building the `WeatherPage` view we previously created and use `BlocProvider` to inject our `ThemeCubit` which handles theme data.
Our `app.dart` widget will handle building the `WeatherPage` view we previously created and use `BlocProvider` to inject our `WeatherCubit`.

<RemoteCode
url="https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart"
Expand Down
29 changes: 7 additions & 22 deletions examples/flutter_weather/.metadata
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
# This file should be version controlled and should not be manually edited.

version:
revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
channel: beta
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
- platform: android
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
- platform: ios
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
- platform: linux
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
- platform: macos
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: web
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
- platform: windows
create_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
base_revision: b1c77b7ed32346fe829c0ca97bd85d19290d54ae
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49

# User provided section

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion examples/flutter_weather/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
Expand Down Expand Up @@ -452,7 +452,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -579,7 +579,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -628,7 +628,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
57 changes: 35 additions & 22 deletions examples/flutter_weather/lib/app.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_weather/theme/theme.dart';
import 'package:flutter_weather/weather/weather.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:weather_repository/weather_repository.dart';
import 'package:weather_repository/weather_repository.dart'
show WeatherRepository;

class WeatherApp extends StatelessWidget {
const WeatherApp({required WeatherRepository weatherRepository, super.key})
Expand All @@ -13,12 +13,9 @@ class WeatherApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: _weatherRepository,
child: BlocProvider(
create: (_) => ThemeCubit(),
child: const WeatherAppView(),
),
return BlocProvider(
create: (_) => WeatherCubit(_weatherRepository),
child: const WeatherAppView(),
);
}
}
Expand All @@ -28,20 +25,36 @@ class WeatherAppView extends StatelessWidget {

@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeCubit, Color>(
builder: (context, color) {
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
),
colorScheme: ColorScheme.fromSeed(seedColor: color),
textTheme: GoogleFonts.rajdhaniTextTheme(),
),
home: const WeatherPage(),
);
},
final seedColor = context.select(
(WeatherCubit cubit) => cubit.state.weather.toColor,
);
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
),
colorScheme: ColorScheme.fromSeed(seedColor: seedColor),
textTheme: GoogleFonts.rajdhaniTextTheme(),
),
home: const WeatherPage(),
);
}
}

extension on Weather {
Color get toColor {
switch (condition) {
case WeatherCondition.clear:
return Colors.yellow;
case WeatherCondition.snowy:
return Colors.lightBlueAccent;
case WeatherCondition.cloudy:
return Colors.blueGrey;
case WeatherCondition.rainy:
return Colors.indigoAccent;
case WeatherCondition.unknown:
return Colors.cyan;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import 'package:flutter_weather/weather/weather.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage._();

static Route<void> route(WeatherCubit weatherCubit) {
static Route<void> route() {
return MaterialPageRoute<void>(
builder: (_) => BlocProvider.value(
value: weatherCubit,
child: const SettingsPage._(),
),
builder: (_) => const SettingsPage._(),
);
}

Expand Down
40 changes: 0 additions & 40 deletions examples/flutter_weather/lib/theme/cubit/theme_cubit.dart

This file was deleted.

1 change: 0 additions & 1 deletion examples/flutter_weather/lib/theme/theme.dart

This file was deleted.

52 changes: 11 additions & 41 deletions examples/flutter_weather/lib/weather/view/weather_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_weather/search/search.dart';
import 'package:flutter_weather/settings/settings.dart';
import 'package:flutter_weather/theme/theme.dart';
import 'package:flutter_weather/weather/weather.dart';
import 'package:weather_repository/weather_repository.dart';

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

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WeatherCubit(context.read<WeatherRepository>()),
child: const WeatherView(),
);
}
}

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

@override
State<WeatherView> createState() => _WeatherViewState();
}

class _WeatherViewState extends State<WeatherView> {
@override
Widget build(BuildContext context) {
return Scaffold(
Expand All @@ -34,38 +15,27 @@ class _WeatherViewState extends State<WeatherView> {
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.of(context).push<void>(
SettingsPage.route(context.read<WeatherCubit>()),
);
},
onPressed: () => Navigator.of(context).push<void>(
SettingsPage.route(),
),
),
],
),
body: Center(
child: BlocConsumer<WeatherCubit, WeatherState>(
listener: (context, state) {
if (state.status.isSuccess) {
context.read<ThemeCubit>().updateTheme(state.weather);
}
},
child: BlocBuilder<WeatherCubit, WeatherState>(
builder: (context, state) {
switch (state.status) {
case WeatherStatus.initial:
return const WeatherEmpty();
case WeatherStatus.loading:
return const WeatherLoading();
case WeatherStatus.success:
return WeatherPopulated(
return switch (state.status) {
WeatherStatus.initial => const WeatherEmpty(),
WeatherStatus.loading => const WeatherLoading(),
WeatherStatus.failure => const WeatherError(),
WeatherStatus.success => WeatherPopulated(
weather: state.weather,
units: state.temperatureUnits,
onRefresh: () {
return context.read<WeatherCubit>().refreshWeather();
},
);
case WeatherStatus.failure:
return const WeatherError();
}
),
};
},
),
),
Expand Down
Loading