Intro
This repo base on management package is flutter_bloc. The app has been setup to work with retrofit, dio, json_annotation, intl_utils and shimmer
Getting Started
- Install Flutter SDK. Require Flutter >=3.3.0
- Install plugins in Android Studio (optional)
- Clone the repo.
- Run
flutter pub get - Run
flutter pub run intl_utils:generate - Run
flutter pub run build_runner build --delete-conflicting-outputs - Run app.
File structure
assets
+---font
+---image
+---2.0x
+---3.0x
libs
+---bloc
| +---app_cubit.dart
| +---app_state.dart
+---common
| +---app_colors.dart
| +---app_dimens.dart
| +---app_images.dart
| +---app_shadows.dart
| +---app_text_styles.dart
| +---app_themes.dart
+---configs
| +---app_configs.dart
+---database
| +---secure_storage_helper.dart
| +---shared_preferences_helper.dart
| +---...
+---l10n
+---models
| +---entities
| | +---user_entity.dart
| | +---...
| +---enums
| | +---load_status.dart
| | +---...
| +---params
| | +---sign_up_param.dart
| | +---...
| +---response
| +---array_response.dart
| +---object_response.dart
+---networks
| +---api_client.dart
| +---api_interceptors.dart
| +---api_util.dart
+---router
| +---route_config.dart
+---repositories
| +---auth_repository.dart
| +---user_repository.dart.dart
| +---...
+---ui
| +---commons
| | +---app_bottom_sheet.dart
| | +---app_dialog.dart
| | +---app_snackbar.dart
| | +---...
| +---pages
| | +---splash
| | | +---splash_page.dart
| | | +---splash_cubit.dart
| | | +---splash_state.dart
| | +---...
| +---widget //Chua cac widget base cho app
| +---appbar
| +---buttons
| | +---app_button.dart
| | +---app_icon_button.dart
| | +---...
| +---images
| | +---app_cache_image.dart
| | +---app_circle_avatar.dart
| +---textfields
| +---shimmer
| +---...
+---utils
| +---date_utils.dart
| +---file_utils.dart
| +---logger.dart
| +---utils.dart
|---main.dart
|---main_dev.dart //Config moi truong dev
+---main_staging.dart //Config moi truong production
| Item | Explaint |
|---|---|
| main.dart: | the "entry point" of program. |
| assets: | store static assests like fonts and images. |
| common: | contain colors, textStyle, theme, ... |
| configs: | hold the configs of your application. |
| database: | container database helper class |
| l10n: | contain all localized string. See more |
| models: | contain entity, enum, .. |
| networks: | |
| router: | contain the route navigation |
| repositories: | contain repository |
| ui | |
| utils |
How to use
Creating a screen.
All screen should be created in the ui/pages folder
Each screen have 3 file:
Logic: movies_cubit.dart
MovieRepository movieRepo;
MoviesCubit({
required this.movieRepo,
}) : super(const MoviesState());
void fetchMovies() async {
emit(state.copyWith(loadMovieStatus: LoadStatus.loading));
try {
final result = await movieRepo.getMovies();
emit(state.copyWith(
loadMovieStatus: LoadStatus.success,
movies: result.results,
));
} catch (e) {
emit(state.copyWith(loadMovieStatus: LoadStatus.failure));
}
}
}
State: movies_state.dart
final LoadStatus loadMovieStatus;
final List<MovieEntity> movies;
const MoviesState({
this.loadMovieStatus = LoadStatus.initial,
this.movies = const [],
});
@override
List<Object?> get props => [
loadMovieStatus,
movies,
];
MoviesState copyWith({
LoadStatus? loadMovieStatus,
List<MovieEntity>? movies,
}) {
return MoviesState(
loadMovieStatus: loadMovieStatus ?? this.loadMovieStatus,
movies: movies ?? this.movies,
);
}
}
View: movies_view.dart
...
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
return MoviesCubit(
movieRepo: context.read<MovieRepository>(),
);
},
child: MoviesChildPage(),
);
}
}
class MoviesChildPage extends StatefulWidget {
...
@override
State<MoviesChildPage> createState() => _MoviesChildPageState();
}
class _MoviesChildPageState extends State<MoviesChildPage> {
late MoviesCubit _cubit;
@override
void initState() {
_cubit = BlocProvider.of<MoviesCubit>(context);
super.initState();
_cubit.fetchInitialMovies();
}
@override
Widget build(BuildContext context) {
super.build(context);
return ...;
}
...
}
Creating API.
- Create entity object in folder
lib/models/entitiesEx:movie_entity.dart
part 'movie_entity.g.dart';
@JsonSerializable()
class MovieEntity {
@JsonKey()
String? title;
...
factory MovieEntity.fromJson(Map<String, dynamic> json) => _$MovieEntityFromJson(json);
Map<String, dynamic> toJson() => _$MovieEntityToJson(this);
}
Class must have @JsonSerializable() for generator. Read json_serializable
- Define and Generate your API in file
lib/networks/api_client.dartEx: GET movies
@GET("/3/discover/movie")
Future<ArrayResponse<MovieEntity>> getMovies(@Query('api_key') String apiKey, @Query('page') int page);
Note: Using ArrayResponse and ObjectResponse for generic response
- Require run command line:
flutter pub run build_runner build --delete-conflicting-outputs
- Create repository file for your feature in folder
lib/repositoriesEx:movie_repository.dart
Future<ArrayResponse<MovieEntity>> getMovies();
}
class MovieRepositoryImpl extends MovieRepository {
ApiClient apiClient;
MovieRepositoryImpl({required this.apiClient});
@override
Future<ArrayResponse<MovieEntity>> getMovies() async {
return apiClient.getMovies(...);
}
}
After, add part 'auth_api.dart'; to services/api/api_service
- You can call API in the logic of screen. Ex:
Other
Logger
logger.i("message"); //" INFO: message"
logger.e("message"); //" ERROR: message"
logger.log("very very very long message");
Snackbar
AppSnackbar.showWarning(message: 'Warning');
AppSnackbar.showError(message: 'Error');
Dialog
message: "An error happened. Please check your connection!",
textConfirm: "Retry",
onConfirm: () {
//Do something
},
);
Button UI when call API
return Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: AppTintButton(
title: 'Sign In',
onPressed: _signIn,
isLoading: state.signInStatus.value == LoadStatus.loading,
),
);
});