Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
✨ added UseCaseProviderBridge
  • Loading branch information
sarbagyastha committed Jan 17, 2023
commit 65241f1c6a776ab1a52088c26bb3c4c9e7fb842f
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ class HomeEntity extends Entity {
this.pokemonNameQuery = '',
this.status = HomeStatus.initial,
this.isRefresh = false,
this.lastViewedPokemon = '',
});

final List<PokemonModel> pokemons;
final String pokemonNameQuery;
final HomeStatus status;
final bool isRefresh;
final String lastViewedPokemon;

@override
List<Object?> get props {
return [pokemons, pokemonNameQuery, status, isRefresh];
return [pokemons, pokemonNameQuery, status, isRefresh, lastViewedPokemon];
}

@override
Expand All @@ -27,12 +29,14 @@ class HomeEntity extends Entity {
String? pokemonNameQuery,
HomeStatus? status,
bool? isRefresh,
String? lastViewedPokemon,
}) {
return HomeEntity(
pokemons: pokemons ?? this.pokemons,
pokemonNameQuery: pokemonNameQuery ?? this.pokemonNameQuery,
status: status ?? this.status,
isRefresh: isRefresh ?? this.isRefresh,
lastViewedPokemon: lastViewedPokemon ?? this.lastViewedPokemon,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ class HomeUIOutput extends Output {
required this.pokemons,
required this.status,
required this.isRefresh,
required this.lastViewedPokemon,
});

final List<PokemonModel> pokemons;
final HomeStatus status;
final bool isRefresh;
final String lastViewedPokemon;

@override
List<Object?> get props => [pokemons, status, isRefresh];
List<Object?> get props => [pokemons, status, isRefresh, lastViewedPokemon];
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class HomeUseCase extends UseCase<HomeEntity> {
transformers: [
HomeUIOutputTransformer(),
PokemonSearchInputTransformer(),
LastViewedPokemonInputTransformer(),
],
);

Expand Down Expand Up @@ -76,6 +77,7 @@ class HomeUIOutputTransformer
pokemons: filteredPokemons.toList(growable: false),
status: entity.status,
isRefresh: entity.isRefresh,
lastViewedPokemon: entity.lastViewedPokemon,
);
}
}
Expand All @@ -87,3 +89,17 @@ class PokemonSearchInputTransformer
return entity.copyWith(pokemonNameQuery: input.name);
}
}

class LastViewedPokemonInput extends Input {
LastViewedPokemonInput({required this.name});

final String name;
}

class LastViewedPokemonInputTransformer
extends InputTransformer<HomeEntity, LastViewedPokemonInput> {
@override
HomeEntity transform(HomeEntity entity, LastViewedPokemonInput input) {
return entity.copyWith(lastViewedPokemon: input.name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class HomePresenter
onRetry: useCase.fetchPokemons,
isLoading: output.status == HomeStatus.loading,
hasFailedLoading: output.status == HomeStatus.failed,
lastViewedPokemon: output.lastViewedPokemon,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ class HomeUI extends UI<HomeViewModel> {
bottom: viewModel.isLoading || viewModel.hasFailedLoading
? null
: PokemonSearchField(onChanged: viewModel.onSearch),
actions: [
if (viewModel.lastViewedPokemon.isNotEmpty)
Text.rich(
TextSpan(
text: 'Last Viewed: ',
children: [
TextSpan(
text: viewModel.lastViewedPokemon,
style: textTheme.labelSmall,
),
],
style: textTheme.bodySmall,
),
),
const SizedBox(width: 16),
],
),
body: child,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class HomeViewModel extends ViewModel {
required this.pokemons,
required this.isLoading,
required this.hasFailedLoading,
required this.lastViewedPokemon,
required this.onRetry,
required this.onRefresh,
required this.onSearch,
Expand All @@ -15,11 +16,14 @@ class HomeViewModel extends ViewModel {
final List<PokemonModel> pokemons;
final bool isLoading;
final bool hasFailedLoading;
final String lastViewedPokemon;

final VoidCallback onRetry;
final AsyncCallback onRefresh;
final ValueChanged<String> onSearch;

@override
List<Object?> get props => [pokemons, isLoading, hasFailedLoading];
List<Object?> get props {
return [pokemons, isLoading, hasFailedLoading, lastViewedPokemon];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,35 @@ import 'package:clean_framework_example/features/profile/models/pokemon_profile_

class ProfileEntity extends Entity {
ProfileEntity({
this.name = '',
this.types = const [],
this.description = '',
this.height = 0,
this.weight = 0,
this.stats = const [],
});

final String name;
final List<String> types;
final String description;
final int height;
final int weight;
final List<PokemonStatModel> stats;

@override
List<Object?> get props => [types, description, height, weight, stats];
List<Object?> get props => [name, types, description, height, weight, stats];

@override
ProfileEntity copyWith({
String? name,
List<String>? types,
String? description,
int? height,
int? weight,
List<PokemonStatModel>? stats,
}) {
return ProfileEntity(
name: name ?? this.name,
types: types ?? this.types,
description: description ?? this.description,
height: height ?? this.height,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ProfileUseCase extends UseCase<ProfileEntity> {
final profile = success.profile;

return entity.copyWith(
name: name,
types: profile.types,
height: profile.height,
weight: profile.weight,
Expand Down
21 changes: 19 additions & 2 deletions packages/clean_framework/example/lib/providers.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import 'package:clean_framework/clean_framework.dart';
import 'package:clean_framework_example/core/pokemon/pokemon_external_interface.dart';
import 'package:clean_framework_example/features/home/domain/home_entity.dart';
import 'package:clean_framework_example/features/home/domain/home_use_case.dart';
import 'package:clean_framework_example/features/home/external_interface/pokemon_collection_gateway.dart';
import 'package:clean_framework_example/features/profile/domain/profile_entity.dart';
import 'package:clean_framework_example/features/profile/domain/profile_use_case.dart';
import 'package:clean_framework_example/features/profile/external_interface/pokemon_profile_gateway.dart';
import 'package:clean_framework_example/features/profile/external_interface/pokemon_species_gateway.dart';

final homeUseCaseProvider = UseCaseProvider(HomeUseCase.new);
final homeUseCaseProvider = UseCaseProvider<HomeEntity, HomeUseCase>(
HomeUseCase.new,
(bridge) {
bridge.connect(
profileUseCaseProvider,
selector: (e) => e.name,
(oldPokeName, pokeName) {
if (oldPokeName != pokeName) {
bridge.useCase.setInput(LastViewedPokemonInput(name: pokeName));
}
},
);
},
);

final profileUseCaseProvider = UseCaseProvider.autoDispose(ProfileUseCase.new);
final profileUseCaseProvider = UseCaseProvider<ProfileEntity, ProfileUseCase>(
ProfileUseCase.new,
);

final pokemonCollectionGateway = GatewayProvider(
PokemonCollectionGateway.new,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'dart:async';

import 'package:clean_framework/clean_framework.dart';
import 'package:clean_framework/src/core/clean_framework_provider.dart';
import 'package:clean_framework/src/core/use_case/entity.dart';
import 'package:clean_framework/src/core/use_case/use_case.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meta/meta.dart';

Expand Down Expand Up @@ -45,8 +44,20 @@ abstract class UseCaseProviderBase<E extends Entity, U extends UseCase<E>,

class UseCaseProvider<E extends Entity, U extends UseCase<E>>
extends UseCaseProviderBase<E, U, StateNotifierProvider<U, E>> {
UseCaseProvider(U Function() create)
: super(provider: StateNotifierProvider((_) => create()));
UseCaseProvider(
U Function() create, [
UseCaseProviderConnector<E, U>? connector,
]) : super(
provider: StateNotifierProvider(
(ref) {
final useCase = create();
connector?.call(
UseCaseProviderBridge._(useCase, ref),
);
return useCase;
},
),
);

static const autoDispose = AutoDisposeUseCaseProviderBuilder();

Expand All @@ -56,8 +67,20 @@ class UseCaseProvider<E extends Entity, U extends UseCase<E>>

class AutoDisposeUseCaseProvider<E extends Entity, U extends UseCase<E>>
extends UseCaseProviderBase<E, U, AutoDisposeStateNotifierProvider<U, E>> {
AutoDisposeUseCaseProvider(U Function() create)
: super(provider: StateNotifierProvider.autoDispose((_) => create()));
AutoDisposeUseCaseProvider(
U Function() create, [
UseCaseProviderConnector<E, U>? connector,
]) : super(
provider: StateNotifierProvider.autoDispose(
(ref) {
final useCase = create();
connector?.call(
UseCaseProviderBridge._(useCase, ref),
);
return useCase;
},
),
);

@override
Refreshable<U> buildNotifier() => call().notifier;
Expand All @@ -67,8 +90,32 @@ class AutoDisposeUseCaseProviderBuilder {
const AutoDisposeUseCaseProviderBuilder();

AutoDisposeUseCaseProvider<E, U> call<E extends Entity, U extends UseCase<E>>(
U Function() create,
) {
return AutoDisposeUseCaseProvider(create);
U Function() create, [
UseCaseProviderConnector<E, U>? connector,
]) {
return AutoDisposeUseCaseProvider(create, connector);
}
}

typedef UseCaseProviderConnector<E extends Entity, U extends UseCase<E>> = void
Function(UseCaseProviderBridge<E, U> bridge);

class UseCaseProviderBridge<BE extends Entity, BU extends UseCase<BE>> {
UseCaseProviderBridge._(this.useCase, Ref<BE> ref) : _ref = ref;

final BU useCase;
final Ref<BE> _ref;

void connect<E extends Entity, U extends UseCase<E>, T>(
UseCaseProvider<E, U> provider,
void Function(T? previous, T next) connector, {
required T Function(E entity) selector,
bool fireImmediately = false,
}) {
_ref.listen<T>(
provider().select(selector),
connector,
fireImmediately: fireImmediately,
);
}
}