mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 03:32:18 +00:00
feat(app): Добавить пример со Scope (#5)
* feat(app): Добавить пример со Scope * fix scope * feat: добавить скоуп с внутренней зависимостью от репозитория (#6) Co-authored-by: Artem Barkalov <artembark@gmail.com> * feat: исправить обалсть видимости ProfileScope * feat: добавить фикс namespace плагинов --------- Co-authored-by: PetrovY <y.petrov@friflex.com> Co-authored-by: Artem Barkalov <artembark@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MainScreen extends StatelessWidget {
|
||||
const MainScreen({super.key});
|
||||
@@ -8,7 +9,27 @@ class MainScreen extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Main Screen'),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.push('/profile');
|
||||
},
|
||||
child: const Text('Открыть профиль'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.push('/profile_scope');
|
||||
},
|
||||
child: const Text('Открыть профиль с областью видимости'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import '../../domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileMockRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileMockRepository implements IProfileRepository {
|
||||
@override
|
||||
String get name => 'ProfileMockRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) {
|
||||
return Future.value('MOCK Yura Petrov');
|
||||
}
|
||||
}
|
||||
24
lib/features/profile/data/repository/profile_repository.dart
Normal file
24
lib/features/profile/data/repository/profile_repository.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import '../../domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileRepository implements IProfileRepository {
|
||||
final IHttpClient httpClient;
|
||||
|
||||
ProfileRepository({required this.httpClient});
|
||||
|
||||
@override
|
||||
String get name => 'ProfileRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) async {
|
||||
// Какой-то запрос к серверу
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
// httpClient.get('https://example.com/profile/$id');
|
||||
|
||||
return 'Yura Petrov';
|
||||
}
|
||||
}
|
||||
39
lib/features/profile/domain/bloc/profile_bloc.dart
Normal file
39
lib/features/profile/domain/bloc/profile_bloc.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
|
||||
part 'profile_event.dart';
|
||||
part 'profile_state.dart';
|
||||
|
||||
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
|
||||
// Вам необходимо добавлять только
|
||||
// один обработчик событий в конструкторе
|
||||
on<ProfileEvent>((event, emit) async {
|
||||
if (event is ProfileFetchProfileEvent) {
|
||||
await _fetchProfile(event, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final IProfileRepository _profileRepository;
|
||||
|
||||
Future<void> _fetchProfile(
|
||||
ProfileFetchProfileEvent event,
|
||||
Emitter<ProfileState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(ProfileWaitingState());
|
||||
final data = await _profileRepository.fetchUserProfile(event.id);
|
||||
emit(ProfileSuccessState(data: data));
|
||||
} on Object catch (error, stackTrace) {
|
||||
emit(
|
||||
ProfileErrorState(
|
||||
message: 'Ошибка при загрузке профиля',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/features/profile/domain/bloc/profile_event.dart
Normal file
17
lib/features/profile/domain/bloc/profile_event.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
part of 'profile_bloc.dart';
|
||||
|
||||
sealed class ProfileEvent extends Equatable {
|
||||
const ProfileEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileFetchProfileEvent extends ProfileEvent {
|
||||
final String id;
|
||||
|
||||
const ProfileFetchProfileEvent({required this.id});
|
||||
|
||||
@override
|
||||
List<Object> get props => [id];
|
||||
}
|
||||
36
lib/features/profile/domain/bloc/profile_state.dart
Normal file
36
lib/features/profile/domain/bloc/profile_state.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
part of 'profile_bloc.dart';
|
||||
|
||||
sealed class ProfileState extends Equatable {
|
||||
const ProfileState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileInitialState extends ProfileState {}
|
||||
|
||||
final class ProfileWaitingState extends ProfileState {}
|
||||
|
||||
final class ProfileErrorState extends ProfileState {
|
||||
final String message;
|
||||
final Object error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
const ProfileErrorState({
|
||||
required this.message,
|
||||
required this.error,
|
||||
this.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [message, error];
|
||||
}
|
||||
|
||||
final class ProfileSuccessState extends ProfileState {
|
||||
final Object data;
|
||||
|
||||
const ProfileSuccessState({required this.data});
|
||||
|
||||
@override
|
||||
List<Object> get props => [data];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IProfileRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
abstract interface class IProfileRepository with DiBaseRepo {
|
||||
Future<String> fetchUserProfile(String id);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/features/profile/domain/bloc/profile_bloc.dart';
|
||||
|
||||
// Класс экрана, где мы инициализируем ProfileBloc
|
||||
// и вызываем событие ProfileFetchProfileEvent
|
||||
class ProfileScreen extends StatelessWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profileRepository = context.di.repositories.profileRepository;
|
||||
// Здесь мы инициализируем ProfileBloc
|
||||
// и вызываем событие ProfileFetchProfileEvent
|
||||
// Или любые другие события, которые вам нужны
|
||||
return BlocProvider(
|
||||
create: (context) => ProfileBloc(profileRepository)
|
||||
..add(const ProfileFetchProfileEvent(id: '1')),
|
||||
child: const _ProfileScreenView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Виджет, который отображает UI экрана
|
||||
class _ProfileScreenView extends StatelessWidget {
|
||||
const _ProfileScreenView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Profile'),
|
||||
),
|
||||
body: Center(
|
||||
child: BlocBuilder<ProfileBloc, ProfileState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
ProfileSuccessState() => Text('Data: ${state.props.first}'),
|
||||
ProfileErrorState() => Text('Error: ${state.message}'),
|
||||
_ => const CircularProgressIndicator(),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import '../../domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
/// {@template ProfileScopeMockRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileScopeMockRepository implements IProfileScopeRepository {
|
||||
@override
|
||||
String get name => 'ProfileScopeMockRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) async {
|
||||
return 'MOCK Yura Petrov';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import '../../domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
/// {@template ProfileScopeRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileScopeRepository implements IProfileScopeRepository {
|
||||
final IHttpClient httpClient;
|
||||
|
||||
ProfileScopeRepository({required this.httpClient});
|
||||
|
||||
@override
|
||||
String get name => 'ProfileScopeRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) async {
|
||||
// Какой-то запрос к серверу
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
// httpClient.get('https://example.com/profile/$id');
|
||||
|
||||
return 'Yura Petrov';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
part 'profile_scope_event.dart';
|
||||
part 'profile_scope_state.dart';
|
||||
|
||||
class ProfileScopeBloc extends Bloc<ProfileScopeEvent, ProfileScopeState> {
|
||||
ProfileScopeBloc({required IProfileScopeRepository profileRepository})
|
||||
: _profileRepository = profileRepository,
|
||||
super(ProfileScopeInitialState()) {
|
||||
// Вам необходимо добавлять только
|
||||
// один обработчик событий в конструкторе
|
||||
on<ProfileScopeEvent>((event, emit) async {
|
||||
return switch (event) {
|
||||
ProfileScopeFetchProfileEvent() => await _fetchProfile(event, emit),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
final IProfileScopeRepository _profileRepository;
|
||||
|
||||
Future<void> _fetchProfile(
|
||||
ProfileScopeFetchProfileEvent event,
|
||||
Emitter<ProfileScopeState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(ProfileScopeWaitingState());
|
||||
final data = await _profileRepository.fetchUserProfile(event.id);
|
||||
emit(ProfileScopeSuccessState(data: data));
|
||||
} on Object catch (error, stackTrace) {
|
||||
emit(
|
||||
ProfileScopeErrorState(
|
||||
message: 'Ошибка при загрузке профиля',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
part of 'profile_scope_bloc.dart';
|
||||
|
||||
sealed class ProfileScopeEvent extends Equatable {
|
||||
const ProfileScopeEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileScopeFetchProfileEvent extends ProfileScopeEvent {
|
||||
final String id;
|
||||
|
||||
const ProfileScopeFetchProfileEvent({required this.id});
|
||||
|
||||
@override
|
||||
List<Object> get props => [id];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
part of 'profile_scope_bloc.dart';
|
||||
|
||||
sealed class ProfileScopeState extends Equatable {
|
||||
const ProfileScopeState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileScopeInitialState extends ProfileScopeState {}
|
||||
|
||||
final class ProfileScopeWaitingState extends ProfileScopeState {}
|
||||
|
||||
final class ProfileScopeErrorState extends ProfileScopeState {
|
||||
final String message;
|
||||
final Object error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
const ProfileScopeErrorState({
|
||||
required this.message,
|
||||
required this.error,
|
||||
this.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [message, error];
|
||||
}
|
||||
|
||||
final class ProfileScopeSuccessState extends ProfileScopeState {
|
||||
final Object data;
|
||||
|
||||
const ProfileScopeSuccessState({required this.data});
|
||||
|
||||
@override
|
||||
List<Object> get props => [data];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IProfileScopeRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
abstract interface class IProfileScopeRepository with DiBaseRepo {
|
||||
Future<String> fetchUserProfile(String id);
|
||||
}
|
||||
77
lib/features/profile_scope/presentation/profile_scope.dart
Normal file
77
lib/features/profile_scope/presentation/profile_scope.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/bloc/profile_scope_bloc.dart';
|
||||
|
||||
class ProfileInheritedScope extends InheritedWidget {
|
||||
const ProfileInheritedScope({
|
||||
required this.profileScopeBloc,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ProfileScopeBloc profileScopeBloc;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ProfileInheritedScope oldWidget) =>
|
||||
profileScopeBloc != oldWidget.profileScopeBloc;
|
||||
}
|
||||
|
||||
class ProfileScope extends StatefulWidget {
|
||||
const ProfileScope({
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
static ProfileInheritedScope? maybeOf(
|
||||
BuildContext context, {
|
||||
bool listen = false,
|
||||
}) {
|
||||
return listen
|
||||
? context.dependOnInheritedWidgetOfExactType<ProfileInheritedScope>()
|
||||
: context.getInheritedWidgetOfExactType<ProfileInheritedScope>();
|
||||
}
|
||||
|
||||
static ProfileInheritedScope of(
|
||||
BuildContext context, {
|
||||
bool listen = false,
|
||||
}) {
|
||||
final result = maybeOf(context, listen: listen);
|
||||
|
||||
if (result == null) {
|
||||
throw StateError(
|
||||
'ProfileScope is not found above widget ${context.widget}',
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ProfileScopeState();
|
||||
}
|
||||
|
||||
class _ProfileScopeState extends State<ProfileScope> {
|
||||
late final ProfileScopeBloc _profileScopeBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_profileScopeBloc =
|
||||
ProfileScopeBloc(profileRepository: context.di.repositories.profileScopeRepository);
|
||||
_profileScopeBloc.add(const ProfileScopeFetchProfileEvent(id: '1'));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_profileScopeBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProfileInheritedScope(profileScopeBloc: _profileScopeBloc, child: widget.child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/bloc/profile_scope_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/presentation/profile_scope.dart';
|
||||
|
||||
// Класс экрана, где мы инициализируем ProfileScopeBloc
|
||||
class ProfileScopeScreen extends StatelessWidget {
|
||||
const ProfileScopeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ProfileScope(
|
||||
child: _ProfileScopeView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileScopeView extends StatelessWidget {
|
||||
const _ProfileScopeView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Profile Scope')),
|
||||
body: Center(
|
||||
child: BlocBuilder<ProfileScopeBloc, ProfileScopeState>(
|
||||
bloc: ProfileScope.of(context).profileScopeBloc,
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
ProfileScopeSuccessState() => Text('Data: ${state.props.first}'),
|
||||
ProfileScopeErrorState() => Text('Error: ${state.message}'),
|
||||
_ => const CircularProgressIndicator(),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user