From 2dfa134f78d5c24138936793f9aa6396eae3ff37 Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 10:24:17 +0800 Subject: [PATCH 1/9] chore: provide quote api cubit --- .../service/cubit/quote_api_cubit.dart | 69 +++++++++++++++++++ .../service/cubit/quote_api_state.dart | 31 +++++++++ .../application/service/cubit/quote_dto.dart | 28 ++++++++ 3 files changed, 128 insertions(+) create mode 100644 lib/core/application/service/cubit/quote_api_cubit.dart create mode 100644 lib/core/application/service/cubit/quote_api_state.dart create mode 100644 lib/core/application/service/cubit/quote_dto.dart diff --git a/lib/core/application/service/cubit/quote_api_cubit.dart b/lib/core/application/service/cubit/quote_api_cubit.dart new file mode 100644 index 0000000..8d58197 --- /dev/null +++ b/lib/core/application/service/cubit/quote_api_cubit.dart @@ -0,0 +1,69 @@ +import 'package:flirt/core/application/service/cubit/quote_dto.dart'; +import 'package:flirt/core/domain/models/api_error_response.dart'; +import 'package:flirt/core/domain/models/api_response.dart'; +import 'package:flirt/core/domain/models/quote/quote_response.dart'; +import 'package:flirt/core/domain/repository/quote_repository.dart'; +import 'package:flirt/internal/utils.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'quote_api_state.dart'; + +class QuoteApiCubit extends Cubit { + QuoteApiCubit({required this.quoteRepository}) + : super( + QuoteApiState( + data: QuoteStateDTO( + authors: [], + quotes: [], + ), + ), + ); + final IQuoteRepository quoteRepository; + + /// Get Quote + Future fetchQuote() async { + try { + /// Persist data inside state by emitting the default value of state. If not, it will override the value. + emit(FetchQuoteLoading(state.data)); + + final APIListResponse response = + await quoteRepository.fetchQuote(); + final QuoteResponseDTO quote = QuoteResponseDTO( + author: response.data.first.author, + content: response.data.first.quote, + ); + + /// Appending new value inside the list of the properties of DTO. + state.data.quotes.add(quote); + state.data.authors.add(quote.author); + + // emit FetchQuoteSuccess event + // this set `quotes` in base class and emits an event + emit(FetchQuoteSuccess(state.data, quote)); + + /// This approach is used to demonstrate persistent data storing that you might use on cases where you need to hold data for a series of screens in a module. + /// For example: If you separate a lengthy registration form in to 5 separate screens, you would need to hold that data until the last step. + + // If not for demonstration purposes, this approach should be using code below - emitting state directly. + // emit(QuoteState(data: QuoteStateDTO(quotes: [], authors: []))); + } on APIErrorResponse catch (error) { + emit( + FetchQuoteFailed( + errorCode: error.errorCode!, + message: error.message, + data: state.data, + ), + ); + } catch (e, stackTrace) { + logError(e, stackTrace); + + emit( + FetchQuoteFailed( + errorCode: '$e', + message: 'Something went wrong.', + data: state.data, + ), + ); + } + } +} diff --git a/lib/core/application/service/cubit/quote_api_state.dart b/lib/core/application/service/cubit/quote_api_state.dart new file mode 100644 index 0000000..701945e --- /dev/null +++ b/lib/core/application/service/cubit/quote_api_state.dart @@ -0,0 +1,31 @@ +part of 'quote_api_cubit.dart'; + +class QuoteApiState { + const QuoteApiState({ + required this.data, + }); + + final QuoteStateDTO data; +} + +class FetchQuoteLoading extends QuoteApiState { + FetchQuoteLoading(QuoteStateDTO data) : super(data: data); +} + +class FetchQuoteSuccess extends QuoteApiState { + const FetchQuoteSuccess(QuoteStateDTO data, this.quoteResponse) + : super(data: data); + + final QuoteResponseDTO quoteResponse; +} + +class FetchQuoteFailed extends QuoteApiState { + const FetchQuoteFailed({ + required this.errorCode, + required this.message, + required super.data, + }); + + final String errorCode; + final String message; +} diff --git a/lib/core/application/service/cubit/quote_dto.dart b/lib/core/application/service/cubit/quote_dto.dart new file mode 100644 index 0000000..0b36d26 --- /dev/null +++ b/lib/core/application/service/cubit/quote_dto.dart @@ -0,0 +1,28 @@ +class QuoteRequestDTO { + QuoteRequestDTO({ + required this.id, + required this.data, + }); + + final String id; + final String data; +} + +class QuoteResponseDTO { + QuoteResponseDTO({ + required this.author, + required this.content, + }); + + final String author; + final String content; +} + +class QuoteStateDTO { + QuoteStateDTO({ + required this.quotes, + required this.authors, + }); + final List quotes; + final List authors; +} From 5443855729c3c839d34781be18cb4479ad6a3d4d Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 10:24:34 +0800 Subject: [PATCH 2/9] chore: remove unnecessary cubit --- .../application/service/cubit/home_cubit.dart | 70 ------------------- .../application/service/cubit/home_dto.dart | 28 -------- .../application/service/cubit/home_state.dart | 31 -------- 3 files changed, 129 deletions(-) delete mode 100644 lib/core/module/home/application/service/cubit/home_cubit.dart delete mode 100644 lib/core/module/home/application/service/cubit/home_dto.dart delete mode 100644 lib/core/module/home/application/service/cubit/home_state.dart diff --git a/lib/core/module/home/application/service/cubit/home_cubit.dart b/lib/core/module/home/application/service/cubit/home_cubit.dart deleted file mode 100644 index f474b0b..0000000 --- a/lib/core/module/home/application/service/cubit/home_cubit.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flirt/core/domain/models/api_error_response.dart'; -import 'package:flirt/core/domain/models/api_response.dart'; -import 'package:flirt/core/domain/models/quote/quote_response.dart'; -import 'package:flirt/core/domain/repository/quote_repository.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_dto.dart'; -import 'package:flirt/internal/utils.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'home_state.dart'; - -class HomeCubit extends Cubit { - HomeCubit({required this.quoteRepository}) - : super( - HomeState( - data: QuoteStateDTO( - authors: [], - quotes: [], - ), - ), - ); - - final IQuoteRepository quoteRepository; - - /// Get Quote - Future fetchQuote() async { - try { - /// Persist data inside state by emitting the default value of state. If not, it will override the value. - emit(FetchQuoteLoading(state.data)); - - final APIListResponse response = - await quoteRepository.fetchQuote(); - final QuoteResponseDTO quote = QuoteResponseDTO( - author: response.data.first.author, - content: response.data.first.quote, - ); - - /// Appending new value inside the list of the properties of DTO. - state.data.quotes.add(quote); - state.data.authors.add(quote.author); - - // emit FetchQuoteSuccess event - // this set `quotes` in base class and emits an event - emit(FetchQuoteSuccess(state.data, quote)); - - /// This approach is used to demonstrate persistent data storing that you might use on cases where you need to hold data for a series of screens in a module. - /// For example: If you separate a lengthy registration form in to 5 separate screens, you would need to hold that data until the last step. - - // If not for demonstration purposes, this approach should be using code below - emitting state directly. - // emit(QuoteState(data: QuoteStateDTO(quotes: [], authors: []))); - } on APIErrorResponse catch (error) { - emit( - FetchQuoteFailed( - errorCode: error.errorCode!, - message: error.message, - data: state.data, - ), - ); - } catch (e, stackTrace) { - logError(e, stackTrace); - - emit( - FetchQuoteFailed( - errorCode: '$e', - message: 'Something went wrong.', - data: state.data, - ), - ); - } - } -} diff --git a/lib/core/module/home/application/service/cubit/home_dto.dart b/lib/core/module/home/application/service/cubit/home_dto.dart deleted file mode 100644 index 0b36d26..0000000 --- a/lib/core/module/home/application/service/cubit/home_dto.dart +++ /dev/null @@ -1,28 +0,0 @@ -class QuoteRequestDTO { - QuoteRequestDTO({ - required this.id, - required this.data, - }); - - final String id; - final String data; -} - -class QuoteResponseDTO { - QuoteResponseDTO({ - required this.author, - required this.content, - }); - - final String author; - final String content; -} - -class QuoteStateDTO { - QuoteStateDTO({ - required this.quotes, - required this.authors, - }); - final List quotes; - final List authors; -} diff --git a/lib/core/module/home/application/service/cubit/home_state.dart b/lib/core/module/home/application/service/cubit/home_state.dart deleted file mode 100644 index d913dae..0000000 --- a/lib/core/module/home/application/service/cubit/home_state.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of 'home_cubit.dart'; - -class HomeState { - const HomeState({ - required this.data, - }); - - final QuoteStateDTO data; -} - -class FetchQuoteLoading extends HomeState { - FetchQuoteLoading(QuoteStateDTO data) : super(data: data); -} - -class FetchQuoteSuccess extends HomeState { - const FetchQuoteSuccess(QuoteStateDTO data, this.quoteResponse) - : super(data: data); - - final QuoteResponseDTO quoteResponse; -} - -class FetchQuoteFailed extends HomeState { - const FetchQuoteFailed({ - required this.errorCode, - required this.message, - required super.data, - }); - - final String errorCode; - final String message; -} From 99ae0cef6e1964e143c24b2813466a0cceb3a2d8 Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 10:40:05 +0800 Subject: [PATCH 3/9] chore: apply quote api cubit --- .../home/interfaces/widgets/quotes_card.dart | 16 ++++++++-------- lib/main.dart | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/core/module/home/interfaces/widgets/quotes_card.dart b/lib/core/module/home/interfaces/widgets/quotes_card.dart index cb2dab6..fa607df 100644 --- a/lib/core/module/home/interfaces/widgets/quotes_card.dart +++ b/lib/core/module/home/interfaces/widgets/quotes_card.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; +import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -17,10 +17,10 @@ class _QuotesCardState extends State { @override void initState() { super.initState(); - context.read().fetchQuote(); + context.read().fetchQuote(); _quoteTimer = Timer.periodic( const Duration(seconds: 15), - (_) => context.read().fetchQuote(), + (_) => context.read().fetchQuote(), ); } @@ -34,12 +34,12 @@ class _QuotesCardState extends State { Widget build(BuildContext context) { final double width = MediaQuery.of(context).size.width; - return BlocConsumer( - listenWhen: (HomeState previous, HomeState current) => + return BlocConsumer( + listenWhen: (QuoteApiState previous, QuoteApiState current) => current is FetchQuoteFailed || current is FetchQuoteSuccess || current is FetchQuoteLoading, - listener: (BuildContext context, HomeState state) { + listener: (BuildContext context, QuoteApiState state) { if (state is FetchQuoteFailed) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -53,9 +53,9 @@ class _QuotesCardState extends State { setState(() => textOpacity = 0); } }, - buildWhen: (HomeState previous, HomeState current) => + buildWhen: (QuoteApiState previous, QuoteApiState current) => current is FetchQuoteSuccess, - builder: (BuildContext context, HomeState state) { + builder: (BuildContext context, QuoteApiState state) { if (state is FetchQuoteSuccess) { return Padding( padding: const EdgeInsets.only(top: 15, left: 30, right: 30), diff --git a/lib/main.dart b/lib/main.dart index 4ceed01..e5a6935 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flirt/configs/themes.dart'; +import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; import 'package:flirt/core/infrastructures/repository/quote_repository.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; import 'package:flirt/core/module/home/interfaces/screens/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,8 +20,8 @@ class App extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: >[ - BlocProvider( - create: (BuildContext context) => HomeCubit( + BlocProvider( + create: (BuildContext context) => QuoteApiCubit( quoteRepository: QuoteRepository(), ), ), From 55658d38bbb47f63f06d83a33ea19c2e43c018cc Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 10:41:34 +0800 Subject: [PATCH 4/9] fix: test case --- ...bit_test.dart => quote_api_cubit_test.dart} | 18 +++++++++--------- .../module/home/screens/home_screen_test.dart | 11 ++++++----- .../module/home/screens/quotes_card_test.dart | 10 +++++----- 3 files changed, 20 insertions(+), 19 deletions(-) rename test/unit_test/{module/home/service/cubit/home_cubit_test.dart => quote_api_cubit_test.dart} (74%) diff --git a/test/unit_test/module/home/service/cubit/home_cubit_test.dart b/test/unit_test/quote_api_cubit_test.dart similarity index 74% rename from test/unit_test/module/home/service/cubit/home_cubit_test.dart rename to test/unit_test/quote_api_cubit_test.dart index 236db70..4494308 100644 --- a/test/unit_test/module/home/service/cubit/home_cubit_test.dart +++ b/test/unit_test/quote_api_cubit_test.dart @@ -1,11 +1,11 @@ // ignore_for_file: depend_on_referenced_packages import 'package:bloc_test/bloc_test.dart'; +import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; import 'package:flirt/core/domain/models/api_error_response.dart'; import 'package:flirt/core/domain/models/api_response.dart'; import 'package:flirt/core/domain/models/quote/quote_response.dart'; import 'package:flirt/core/infrastructures/repository/quote_repository.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -18,7 +18,7 @@ void main() { mockQuoteRepository = MockQuoteRepository(); }); group('Quote cubit.', () { - blocTest( + blocTest( 'On successful fetch quote, it should emit FetchQuoteSuccess.', build: () { when(() => mockQuoteRepository.fetchQuote()).thenAnswer((_) async { @@ -31,16 +31,16 @@ void main() { ); }); - return HomeCubit(quoteRepository: mockQuoteRepository); + return QuoteApiCubit(quoteRepository: mockQuoteRepository); }, - act: (HomeCubit cubit) => cubit.fetchQuote(), - expect: () => >[ + act: (QuoteApiCubit cubit) => cubit.fetchQuote(), + expect: () => >[ isA(), isA(), ], ); - blocTest( + blocTest( 'On failed fetch quote, it should emit FetchQuoteFailed.', build: () { when(() => mockQuoteRepository.fetchQuote()).thenThrow( @@ -50,10 +50,10 @@ void main() { ), ); - return HomeCubit(quoteRepository: mockQuoteRepository); + return QuoteApiCubit(quoteRepository: mockQuoteRepository); }, - act: (HomeCubit cubit) => cubit.fetchQuote(), - expect: () => >[ + act: (QuoteApiCubit cubit) => cubit.fetchQuote(), + expect: () => >[ isA(), isA(), ], diff --git a/test/widget_test/module/home/screens/home_screen_test.dart b/test/widget_test/module/home/screens/home_screen_test.dart index 1532e15..a5204c6 100644 --- a/test/widget_test/module/home/screens/home_screen_test.dart +++ b/test/widget_test/module/home/screens/home_screen_test.dart @@ -1,6 +1,6 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_dto.dart'; +import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; +import 'package:flirt/core/application/service/cubit/quote_dto.dart'; import 'package:flirt/core/module/home/interfaces/screens/home_screen.dart'; import 'package:flirt/core/module/home/interfaces/widgets/quotes_card.dart'; import 'package:flutter/material.dart'; @@ -8,7 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -class MockQuoteCubit extends MockCubit implements HomeCubit {} +class MockQuoteCubit extends MockCubit + implements QuoteApiCubit {} void main() { late MockQuoteCubit mockQuoteCubit; @@ -18,7 +19,7 @@ void main() { }); Future pumpWidget(WidgetTester tester) async => tester.pumpWidget( - BlocProvider( + BlocProvider( create: (BuildContext context) => mockQuoteCubit, child: const MaterialApp( home: HomeScreen(), @@ -28,7 +29,7 @@ void main() { void listenStub() { when(() => mockQuoteCubit.state).thenReturn( - HomeState( + QuoteApiState( data: QuoteStateDTO(authors: [], quotes: []), ), ); diff --git a/test/widget_test/module/home/screens/quotes_card_test.dart b/test/widget_test/module/home/screens/quotes_card_test.dart index f1ba9a7..d48f03d 100644 --- a/test/widget_test/module/home/screens/quotes_card_test.dart +++ b/test/widget_test/module/home/screens/quotes_card_test.dart @@ -1,6 +1,6 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; -import 'package:flirt/core/module/home/application/service/cubit/home_dto.dart'; +import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; +import 'package:flirt/core/application/service/cubit/quote_dto.dart'; import 'package:flirt/core/module/home/interfaces/widgets/quotes_card.dart'; import 'package:flirt/test/main_test.dart'; import 'package:flutter/material.dart'; @@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -class MockHomeCubit extends MockCubit implements HomeCubit {} +class MockHomeCubit extends MockCubit implements QuoteApiCubit {} void main() { late MockHomeCubit mockHomeCubit; @@ -18,7 +18,7 @@ void main() { }); Future widgetPumper(WidgetTester tester) => tester.pumpWidget( - BlocProvider( + BlocProvider( create: (BuildContext context) => mockHomeCubit, child: universalPumper( const Scaffold( @@ -30,7 +30,7 @@ void main() { void initializeListener() { when(() => mockHomeCubit.state).thenReturn( - HomeState( + QuoteApiState( data: QuoteStateDTO(quotes: [], authors: []), ), ); From 925e15932ee53861fee3107f0b6e163e6d323c12 Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 12:30:42 +0800 Subject: [PATCH 5/9] chore: apply proper casing --- .../service/cubit/quote_api_cubit.dart | 6 +++--- .../service/cubit/quote_api_state.dart | 10 +++++----- .../home/interfaces/widgets/quotes_card.dart | 14 +++++++------- lib/main.dart | 4 ++-- test/unit_test/quote_api_cubit_test.dart | 16 ++++++++-------- .../module/home/screens/home_screen_test.dart | 8 ++++---- .../module/home/screens/quotes_card_test.dart | 6 +++--- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/core/application/service/cubit/quote_api_cubit.dart b/lib/core/application/service/cubit/quote_api_cubit.dart index 8d58197..ef8d915 100644 --- a/lib/core/application/service/cubit/quote_api_cubit.dart +++ b/lib/core/application/service/cubit/quote_api_cubit.dart @@ -8,10 +8,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; part 'quote_api_state.dart'; -class QuoteApiCubit extends Cubit { - QuoteApiCubit({required this.quoteRepository}) +class QuoteAPICubit extends Cubit { + QuoteAPICubit({required this.quoteRepository}) : super( - QuoteApiState( + QuoteAPIState( data: QuoteStateDTO( authors: [], quotes: [], diff --git a/lib/core/application/service/cubit/quote_api_state.dart b/lib/core/application/service/cubit/quote_api_state.dart index 701945e..a6d7de8 100644 --- a/lib/core/application/service/cubit/quote_api_state.dart +++ b/lib/core/application/service/cubit/quote_api_state.dart @@ -1,25 +1,25 @@ part of 'quote_api_cubit.dart'; -class QuoteApiState { - const QuoteApiState({ +class QuoteAPIState { + const QuoteAPIState({ required this.data, }); final QuoteStateDTO data; } -class FetchQuoteLoading extends QuoteApiState { +class FetchQuoteLoading extends QuoteAPIState { FetchQuoteLoading(QuoteStateDTO data) : super(data: data); } -class FetchQuoteSuccess extends QuoteApiState { +class FetchQuoteSuccess extends QuoteAPIState { const FetchQuoteSuccess(QuoteStateDTO data, this.quoteResponse) : super(data: data); final QuoteResponseDTO quoteResponse; } -class FetchQuoteFailed extends QuoteApiState { +class FetchQuoteFailed extends QuoteAPIState { const FetchQuoteFailed({ required this.errorCode, required this.message, diff --git a/lib/core/module/home/interfaces/widgets/quotes_card.dart b/lib/core/module/home/interfaces/widgets/quotes_card.dart index fa607df..de7e4ff 100644 --- a/lib/core/module/home/interfaces/widgets/quotes_card.dart +++ b/lib/core/module/home/interfaces/widgets/quotes_card.dart @@ -17,10 +17,10 @@ class _QuotesCardState extends State { @override void initState() { super.initState(); - context.read().fetchQuote(); + context.read().fetchQuote(); _quoteTimer = Timer.periodic( const Duration(seconds: 15), - (_) => context.read().fetchQuote(), + (_) => context.read().fetchQuote(), ); } @@ -34,12 +34,12 @@ class _QuotesCardState extends State { Widget build(BuildContext context) { final double width = MediaQuery.of(context).size.width; - return BlocConsumer( - listenWhen: (QuoteApiState previous, QuoteApiState current) => + return BlocConsumer( + listenWhen: (QuoteAPIState previous, QuoteAPIState current) => current is FetchQuoteFailed || current is FetchQuoteSuccess || current is FetchQuoteLoading, - listener: (BuildContext context, QuoteApiState state) { + listener: (BuildContext context, QuoteAPIState state) { if (state is FetchQuoteFailed) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -53,9 +53,9 @@ class _QuotesCardState extends State { setState(() => textOpacity = 0); } }, - buildWhen: (QuoteApiState previous, QuoteApiState current) => + buildWhen: (QuoteAPIState previous, QuoteAPIState current) => current is FetchQuoteSuccess, - builder: (BuildContext context, QuoteApiState state) { + builder: (BuildContext context, QuoteAPIState state) { if (state is FetchQuoteSuccess) { return Padding( padding: const EdgeInsets.only(top: 15, left: 30, right: 30), diff --git a/lib/main.dart b/lib/main.dart index e5a6935..ab19205 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,8 +20,8 @@ class App extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: >[ - BlocProvider( - create: (BuildContext context) => QuoteApiCubit( + BlocProvider( + create: (BuildContext context) => QuoteAPICubit( quoteRepository: QuoteRepository(), ), ), diff --git a/test/unit_test/quote_api_cubit_test.dart b/test/unit_test/quote_api_cubit_test.dart index 4494308..8c8961a 100644 --- a/test/unit_test/quote_api_cubit_test.dart +++ b/test/unit_test/quote_api_cubit_test.dart @@ -18,7 +18,7 @@ void main() { mockQuoteRepository = MockQuoteRepository(); }); group('Quote cubit.', () { - blocTest( + blocTest( 'On successful fetch quote, it should emit FetchQuoteSuccess.', build: () { when(() => mockQuoteRepository.fetchQuote()).thenAnswer((_) async { @@ -31,16 +31,16 @@ void main() { ); }); - return QuoteApiCubit(quoteRepository: mockQuoteRepository); + return QuoteAPICubit(quoteRepository: mockQuoteRepository); }, - act: (QuoteApiCubit cubit) => cubit.fetchQuote(), - expect: () => >[ + act: (QuoteAPICubit cubit) => cubit.fetchQuote(), + expect: () => >[ isA(), isA(), ], ); - blocTest( + blocTest( 'On failed fetch quote, it should emit FetchQuoteFailed.', build: () { when(() => mockQuoteRepository.fetchQuote()).thenThrow( @@ -50,10 +50,10 @@ void main() { ), ); - return QuoteApiCubit(quoteRepository: mockQuoteRepository); + return QuoteAPICubit(quoteRepository: mockQuoteRepository); }, - act: (QuoteApiCubit cubit) => cubit.fetchQuote(), - expect: () => >[ + act: (QuoteAPICubit cubit) => cubit.fetchQuote(), + expect: () => >[ isA(), isA(), ], diff --git a/test/widget_test/module/home/screens/home_screen_test.dart b/test/widget_test/module/home/screens/home_screen_test.dart index a5204c6..e6fbcb5 100644 --- a/test/widget_test/module/home/screens/home_screen_test.dart +++ b/test/widget_test/module/home/screens/home_screen_test.dart @@ -8,8 +8,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -class MockQuoteCubit extends MockCubit - implements QuoteApiCubit {} +class MockQuoteCubit extends MockCubit + implements QuoteAPICubit {} void main() { late MockQuoteCubit mockQuoteCubit; @@ -19,7 +19,7 @@ void main() { }); Future pumpWidget(WidgetTester tester) async => tester.pumpWidget( - BlocProvider( + BlocProvider( create: (BuildContext context) => mockQuoteCubit, child: const MaterialApp( home: HomeScreen(), @@ -29,7 +29,7 @@ void main() { void listenStub() { when(() => mockQuoteCubit.state).thenReturn( - QuoteApiState( + QuoteAPIState( data: QuoteStateDTO(authors: [], quotes: []), ), ); diff --git a/test/widget_test/module/home/screens/quotes_card_test.dart b/test/widget_test/module/home/screens/quotes_card_test.dart index d48f03d..20e308e 100644 --- a/test/widget_test/module/home/screens/quotes_card_test.dart +++ b/test/widget_test/module/home/screens/quotes_card_test.dart @@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -class MockHomeCubit extends MockCubit implements QuoteApiCubit {} +class MockHomeCubit extends MockCubit implements QuoteAPICubit {} void main() { late MockHomeCubit mockHomeCubit; @@ -18,7 +18,7 @@ void main() { }); Future widgetPumper(WidgetTester tester) => tester.pumpWidget( - BlocProvider( + BlocProvider( create: (BuildContext context) => mockHomeCubit, child: universalPumper( const Scaffold( @@ -30,7 +30,7 @@ void main() { void initializeListener() { when(() => mockHomeCubit.state).thenReturn( - QuoteApiState( + QuoteAPIState( data: QuoteStateDTO(quotes: [], authors: []), ), ); From 90fc40bf27d95c0a1b519f6da139534b2141d5ae Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 15:18:31 +0800 Subject: [PATCH 6/9] chore: make api cubit stateless --- .../service/cubit/quote_api_cubit.dart | 28 +++---------------- .../service/cubit/quote_api_state.dart | 18 +++--------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/lib/core/application/service/cubit/quote_api_cubit.dart b/lib/core/application/service/cubit/quote_api_cubit.dart index ef8d915..7c6c87e 100644 --- a/lib/core/application/service/cubit/quote_api_cubit.dart +++ b/lib/core/application/service/cubit/quote_api_cubit.dart @@ -9,49 +9,30 @@ import 'package:flutter_bloc/flutter_bloc.dart'; part 'quote_api_state.dart'; class QuoteAPICubit extends Cubit { - QuoteAPICubit({required this.quoteRepository}) - : super( - QuoteAPIState( - data: QuoteStateDTO( - authors: [], - quotes: [], - ), - ), - ); + QuoteAPICubit({required this.quoteRepository}) : super(QuoteAPIState()); final IQuoteRepository quoteRepository; /// Get Quote Future fetchQuote() async { try { - /// Persist data inside state by emitting the default value of state. If not, it will override the value. - emit(FetchQuoteLoading(state.data)); + emit(FetchQuoteLoading()); final APIListResponse response = await quoteRepository.fetchQuote(); + final QuoteResponseDTO quote = QuoteResponseDTO( author: response.data.first.author, content: response.data.first.quote, ); - /// Appending new value inside the list of the properties of DTO. - state.data.quotes.add(quote); - state.data.authors.add(quote.author); - // emit FetchQuoteSuccess event // this set `quotes` in base class and emits an event - emit(FetchQuoteSuccess(state.data, quote)); - - /// This approach is used to demonstrate persistent data storing that you might use on cases where you need to hold data for a series of screens in a module. - /// For example: If you separate a lengthy registration form in to 5 separate screens, you would need to hold that data until the last step. - - // If not for demonstration purposes, this approach should be using code below - emitting state directly. - // emit(QuoteState(data: QuoteStateDTO(quotes: [], authors: []))); + emit(FetchQuoteSuccess(quote)); } on APIErrorResponse catch (error) { emit( FetchQuoteFailed( errorCode: error.errorCode!, message: error.message, - data: state.data, ), ); } catch (e, stackTrace) { @@ -61,7 +42,6 @@ class QuoteAPICubit extends Cubit { FetchQuoteFailed( errorCode: '$e', message: 'Something went wrong.', - data: state.data, ), ); } diff --git a/lib/core/application/service/cubit/quote_api_state.dart b/lib/core/application/service/cubit/quote_api_state.dart index a6d7de8..501e459 100644 --- a/lib/core/application/service/cubit/quote_api_state.dart +++ b/lib/core/application/service/cubit/quote_api_state.dart @@ -1,29 +1,19 @@ part of 'quote_api_cubit.dart'; -class QuoteAPIState { - const QuoteAPIState({ - required this.data, - }); - - final QuoteStateDTO data; -} +class QuoteAPIState {} -class FetchQuoteLoading extends QuoteAPIState { - FetchQuoteLoading(QuoteStateDTO data) : super(data: data); -} +class FetchQuoteLoading extends QuoteAPIState {} class FetchQuoteSuccess extends QuoteAPIState { - const FetchQuoteSuccess(QuoteStateDTO data, this.quoteResponse) - : super(data: data); + FetchQuoteSuccess(this.quoteResponse); final QuoteResponseDTO quoteResponse; } class FetchQuoteFailed extends QuoteAPIState { - const FetchQuoteFailed({ + FetchQuoteFailed({ required this.errorCode, required this.message, - required super.data, }); final String errorCode; From 7c7129ee3c658182276e4ad7fb17d7358130fd14 Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 15:20:38 +0800 Subject: [PATCH 7/9] chore: provide local stateful cubit --- .../application/service/cubit/home_cubit.dart | 27 +++++++++++++++++++ .../application/service/cubit/home_state.dart | 9 +++++++ lib/main.dart | 4 +++ 3 files changed, 40 insertions(+) create mode 100644 lib/core/module/home/application/service/cubit/home_cubit.dart create mode 100644 lib/core/module/home/application/service/cubit/home_state.dart diff --git a/lib/core/module/home/application/service/cubit/home_cubit.dart b/lib/core/module/home/application/service/cubit/home_cubit.dart new file mode 100644 index 0000000..fb849fc --- /dev/null +++ b/lib/core/module/home/application/service/cubit/home_cubit.dart @@ -0,0 +1,27 @@ +import 'package:flirt/core/application/service/cubit/quote_dto.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'home_state.dart'; + +class HomeCubit extends Cubit { + HomeCubit() + : super( + HomeState( + data: QuoteStateDTO( + authors: [], + quotes: [], + ), + ), + ); + + /// This method is used to store a new quote and its author in the state. + /// It appends the quote to the list of quotes and the author to the list of authors. + /// + /// [quote] Single response from the quote + Future storeState(QuoteResponseDTO quote) async { + /// This approach is used to demonstrate persistent data storing that you might use on cases where you need to hold data for a series of screens in a module. + /// For example: If you separate a lengthy registration form in to 5 separate screens, you would need to hold that data until the last step. + state.data.quotes.add(quote); + state.data.authors.add(quote.author); + } +} diff --git a/lib/core/module/home/application/service/cubit/home_state.dart b/lib/core/module/home/application/service/cubit/home_state.dart new file mode 100644 index 0000000..9a293b3 --- /dev/null +++ b/lib/core/module/home/application/service/cubit/home_state.dart @@ -0,0 +1,9 @@ +part of 'home_cubit.dart'; + +class HomeState { + HomeState({ + required this.data, + }); + + final QuoteStateDTO data; +} diff --git a/lib/main.dart b/lib/main.dart index ab19205..b6bccd0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flirt/configs/themes.dart'; import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; import 'package:flirt/core/infrastructures/repository/quote_repository.dart'; +import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; import 'package:flirt/core/module/home/interfaces/screens/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,6 +26,9 @@ class App extends StatelessWidget { quoteRepository: QuoteRepository(), ), ), + BlocProvider( + create: (BuildContext context) => HomeCubit(), + ), ], child: MaterialApp( home: _HomePageState(), From c246cf8e7f33793fc380673149ccc46fd82014e5 Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 15:21:18 +0800 Subject: [PATCH 8/9] chore: apply local storing --- lib/core/module/home/interfaces/widgets/quotes_card.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/core/module/home/interfaces/widgets/quotes_card.dart b/lib/core/module/home/interfaces/widgets/quotes_card.dart index de7e4ff..3963071 100644 --- a/lib/core/module/home/interfaces/widgets/quotes_card.dart +++ b/lib/core/module/home/interfaces/widgets/quotes_card.dart @@ -1,5 +1,7 @@ import 'dart:async'; + import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; +import 'package:flirt/core/module/home/application/service/cubit/home_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -49,6 +51,8 @@ class _QuotesCardState extends State { ); } else if (state is FetchQuoteSuccess) { setState(() => textOpacity = 1.0); + + context.read().storeState(state.quoteResponse); } else if (state is FetchQuoteLoading) { setState(() => textOpacity = 0); } From 87d82cb1c56e65af3e9eca5151f947fb1013c95c Mon Sep 17 00:00:00 2001 From: josephgcedeno Date: Thu, 31 Oct 2024 15:21:26 +0800 Subject: [PATCH 9/9] fix: test case --- .../module/home/screens/home_screen_test.dart | 5 +---- .../module/home/screens/quotes_card_test.dart | 13 ++----------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/test/widget_test/module/home/screens/home_screen_test.dart b/test/widget_test/module/home/screens/home_screen_test.dart index e6fbcb5..10c6af9 100644 --- a/test/widget_test/module/home/screens/home_screen_test.dart +++ b/test/widget_test/module/home/screens/home_screen_test.dart @@ -1,6 +1,5 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flirt/core/application/service/cubit/quote_api_cubit.dart'; -import 'package:flirt/core/application/service/cubit/quote_dto.dart'; import 'package:flirt/core/module/home/interfaces/screens/home_screen.dart'; import 'package:flirt/core/module/home/interfaces/widgets/quotes_card.dart'; import 'package:flutter/material.dart'; @@ -29,9 +28,7 @@ void main() { void listenStub() { when(() => mockQuoteCubit.state).thenReturn( - QuoteAPIState( - data: QuoteStateDTO(authors: [], quotes: []), - ), + QuoteAPIState(), ); when(() => mockQuoteCubit.fetchQuote()).thenAnswer((_) async {}); } diff --git a/test/widget_test/module/home/screens/quotes_card_test.dart b/test/widget_test/module/home/screens/quotes_card_test.dart index 20e308e..0b8c110 100644 --- a/test/widget_test/module/home/screens/quotes_card_test.dart +++ b/test/widget_test/module/home/screens/quotes_card_test.dart @@ -30,9 +30,7 @@ void main() { void initializeListener() { when(() => mockHomeCubit.state).thenReturn( - QuoteAPIState( - data: QuoteStateDTO(quotes: [], authors: []), - ), + QuoteAPIState(), ); when(() => mockHomeCubit.fetchQuote()).thenAnswer((_) async {}); } @@ -67,10 +65,6 @@ void main() { FetchQuoteFailed( errorCode: '', message: errorMessage, - data: QuoteStateDTO( - quotes: [], - authors: [], - ), ), ]), ); @@ -92,7 +86,6 @@ void main() { mockHomeCubit, Stream.fromIterable([ FetchQuoteSuccess( - QuoteStateDTO(quotes: [], authors: []), QuoteResponseDTO( author: author, content: content, @@ -114,9 +107,7 @@ void main() { whenListen( mockHomeCubit, Stream.fromIterable([ - FetchQuoteLoading( - QuoteStateDTO(quotes: [], authors: []), - ), + FetchQuoteLoading(), ]), ); initializeListener();