Skip to content

newwavesolutions/flutter-base

Repository files navigation

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

  1. Install Flutter SDK. Require Flutter >=3.3.0
  2. Install plugins in Android Studio (optional)
  3. Clone the repo.
  4. Run flutter pub get
  5. Run flutter pub run intl_utils:generate
  6. Run flutter pub run build_runner build --delete-conflicting-outputs
  7. 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 //Chứa các 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 môi trường dev
└───main_staging.dart //Config môi trường 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

class MoviesCubit extends Cubit<MoviesState> {
  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

class MoviesState extends Equatable {
  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

class MoviesPage extends StatelessWidget {
  ...
  @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.

  1. Create entity object in folder lib/models/entities Ex: movie_entity.dart
import 'package:json_annotation/json_annotation.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

  1. Define and Generate your API in file lib/networks/api_client.dart Ex: GET movies
  /// Movie
  @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

  1. Require run command line:
flutter pub run build_runner build --delete-conflicting-outputs
  1. Create repository file for your feature in folder lib/repositories Ex: movie_repository.dart
abstract class MovieRepository {
  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

  1. You can call API in the logic of screen. Ex:
final result = await movieRepo.getMovies();

Other

Logger

logger.d("message"); //"💙 DEBUG: message"
logger.i("message"); //"💚 INFO: message"
logger.e("message"); //"❤️ ERROR: message"
logger.log("very very very long message");

Snackbar

AppSnackbar.showInfo(message: 'Info');
AppSnackbar.showWarning(message: 'Warning');
AppSnackbar.showError(message: 'Error');

Dialog

AppDialog.defaultDialog(
          message: "An error happened. Please check your connection!",
          textConfirm: "Retry",
          onConfirm: () {
            //Do something
          },
);

Button UI when call API

return Obx(() {
    return Container(
        padding: EdgeInsets.symmetric(horizontal: 20),
        child: AppTintButton(
          title: 'Sign In',
          onPressed: _signIn,
          isLoading: state.signInStatus.value == LoadStatus.loading,
        ),
    );
});