Skip to content

Commit

Permalink
refactor(weather_example): simplify state management and improve layo…
Browse files Browse the repository at this point in the history
…ut (#4216)
  • Loading branch information
felangel authored Jul 28, 2024
1 parent f82f896 commit 8f8b66b
Show file tree
Hide file tree
Showing 18 changed files with 124 additions and 371 deletions.

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
8 changes: 4 additions & 4 deletions examples/flutter_weather/ios/Runner.xcodeproj/project.pbxproj
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;
}
}
}
7 changes: 2 additions & 5 deletions examples/flutter_weather/lib/settings/view/settings_page.dart
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

0 comments on commit 8f8b66b

Please sign in to comment.