Create flutter project with all needed configuration in two minutes (theme, localization, connect to firebase, FCM, local notifications, safe API call, error handling, animation..etc)
Flutter Getx template: Starting up new project with all needed configuration has never been easier.
We all face the same problem when we want to start a new project we have to take care of some repeatable things such as
This project will take care of all this repeatable things so you can start your project in few steps and you will have all the mentioned points set up and ready to use ๐
To make your app responsive and look exactly as your (xd,figma..etc) design you need to set artbord size for flutter_ScreenUtil in main.dart
ScreenUtilInit(
designSize: const Size(375, 812), // change this to your xd artboard size
FCM & Awesome Notifications are initialized in main.dart so when ever you connect your app to firebase your app will be ready to receive notifications you don't need to do anything, if you want to send token to api you can find this function in FcmHelper class ๐
static _sendFcmTokenToServer(){
var token = MySharedPref.getFcmToken();
// TODO SEND FCM TOKEN TO SERVER
}
Change app package name
flutter pub run change_app_package_name:main com.new.package.name
Change app name
flutter pub run rename_app:main all="My App Name"
Change app launch icon (replace assets/images/app_icon.png with your app icon) then run this command
flutter pub run flutter_launcher_icons:main
FCM: firebase has recently added (add flutter app) to your firebase which will make adding our flutter(android/ios) app to firebase take only 2 steps ๐ฅ but first you need to download Firebase CLI and in the terminal execute:
dart pub global activate flutterfire_cli
then follow the firebase guid you will get command similar to this one
flutterfire configure --project=flutter-firebase-YOUR_PROJECT_ID
and that's it! your project is now connected to firebase and fcm is up and ready to get notifications
IOS require few more steps from your side to recive fcm notifications follow the Dcos steps and after that everything should be working fine from flutter side
200.w // adapted to screen width
100.h // /Adapted to screen height
25.sp // adapted font size
10.r // adapted radius
// Example
Container(
height: 100.h,
width: 200.w,
child: Text("Hello",style: TextStyle(fontSize: 20.sp,))
)
Theme
Change theme
MyTheme.changeTheme();
Check current theme
bool isThemeLight = MyTheme.getThemeIsLight();
Localization
Change app locale
LocalizationService.updateLanguage('en');
Get current locale
LocalizationService.getCurrentLocal();
Use translation
Text(Strings.hello.tr)
Safe api call
logic code (in controller)
// hold data coming from api
List<dynamic> data;
// api call status
ApiCallStatus apiCallStatus = ApiCallStatus.holding;
// getting data from api
getData() async {
// *) perform api call
await BaseClient.safeApiCall(
Constants.todosApiUrl, // url
RequestType.get, // request type (get,post,delete,put),
onLoading: () {
// *) indicate loading state
apiCallStatus = ApiCallStatus.loading;
update();
},
onSuccess: (response){ // api done successfully
data = List.from(response.data);
// -) indicate success state
apiCallStatus = ApiCallStatus.success;
update(); // update ui
},
// if you don't pass this method base client
// will automatically handle error and show error message to user
onError: (error){
// show error message to user
BaseClient.handleApiError(error);
// -) indicate error status
apiCallStatus = ApiCallStatus.error;
update(); // update ui
},
);
}
UI: MyWidgetsAnimator will animate between widgets depending on current api call status
GetBuilder<HomeController>(
builder: (controller){
return MyWidgetsAnimator(
apiCallStatus: controller.apiCallStatus,
loadingWidget: () => const Center(child: CircularProgressIndicator(),),
errorWidget: ()=> const Center(child: Text('Something went wrong!'),),
successWidget: () =>
ListView.separated(
itemCount: controller.data!.length,
separatorBuilder: (_,__) => SizedBox(height: 10.h,),
itemBuilder: (ctx,index) => ListTile(
title: Text(controller.data![index]['userId'].toString()),
subtitle: Text(controller.data![index]['title']),
),
),
);
},
)
Snackbars (in app notify):
CustomSnackBar.showCustomSnackBar(title: 'Done successfully!', message: 'item added to wishlist');
CustomSnackBar.showCustomErrorSnackBar(title: 'Failed!', message: 'failed to load data');
CustomSnackBar.showCustomToast(message: 'added to card');
CustomSnackBar.showCustomErrorToast(message: 'added to card');
ย ย ย ย ย ย
After setting up all the needed thing now lets talk about folder structure which is mainly based on Getx Pattern and there are some personal opinions, if you open your lib folder you will find those folders
.
โโโ lib
โโโ app
โ โโโ components
โ โโโ data
โ โ โโโ local
โ โ โโโ models
โ โโโ modules
โ โ โโโ home
โ โโโ routes
โ โโโ services
โโโ config
โ โโโ theme
โ โโโ translation
โโโ utils
Theme: if you opened theme package you will see those files
โโโ theme
โโโ dark_theme_colors.dart
โโโ light_theme_colors.dart
โโโ my_fonts.dart
โโโ my_styles.dart
โโโ my_theme.dart
you only need to change app colors (light/dark_theme_colors) and if you want to change app fonts sizes and family just modify my_fonts.dart and that is it you don't need to worry about styles and theme you only need to edit my_syles.dart if you want to change some element theme data (padding,border..etc) and if you want to change theme just use this code
// change theme and save current theme state to shared pref
MyTheme.changeTheme();
and if you want to check if the theme is dark/light just use
bool themeIsLight = MyTheme.getThemeIsLight();
// OR
bool themeIsLight = MySharedPref.getThemeIsLight();
Localization/translation we will use getx localization system which in the normal case code would look something like this
class LocalizationService extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': { 'hello' : 'Hello' },
'ar_AR': { 'hello' : 'ู
ุฑุญุจุงู' },
};
}
Text('hello'.tr); // translated text
but because we have so many words to translate we will separate keys file (strings_enum.dart) and languages map into different classes so code will become like this
class LocalizationService extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': enUs,
'ar_AR': arAR,
};
}
// keys
class Strings {
static const String hello = 'hello';
}
// english words
const Map<String, String> enUs = {
Strings.hello : 'Hello',
}
// arabic translate
final Map<String, String> arAR = {
Strings.hello : 'ู
ุฑุญุจุง',
}
//result
Text(Strings.hello.tr)
and that explain why we have this file structure inside our translation package
โโโ translations
โโโ ar_Ar
โ โโโ ar_ar_translation.dart
โโโ en_US
โ โโโ en_us_translation.dart
โโโ localization_service.dart
โโโ strings_enum.dart
to change language you will use
LocalizationService.updateLanguage('en');
and to get the current locale/language you can use
LocalizationService.getCurrentLocal();
// OR
MySharedPref.getCurrentLocal();
Safe api call: under if you opened lib/app/services package you will find 3 files
class HomeController extends GetxController {
// hold data
List<dynamic>? data;
// api call status
ApiCallStatus apiCallStatus = ApiCallStatus.holding;
// getting data from api simulating
getData() async {
// *) indicate loading state
apiCallStatus = ApiCallStatus.loading;
update();
// *) perform api call
await BaseClient.safeApiCall(
Constants.todosApiUrl, // url
RequestType.get,
onSuccess: (response){ // api done successfully
data = List.from(response.data);
// -) indicate success state
apiCallStatus = ApiCallStatus.success;
update(); // update ui
},
// if you don't pass this method base client
// will automatically handle error and show message
onError: (error){
// show error message to user
BaseClient.handleApiError(error);
// -) indicate error status
apiCallStatus = ApiCallStatus.error;
update(); // update ui
}, // error while performing request
);
}
@override
void onInit() {
getData();
super.onInit();
}
}
base client will catch all the possible errors and if you didn't pass onError function it will automatically catch the error in UI side code will be
GetBuilder<HomeController>(
builder: (_){
return MyWidgetsAnimator(
apiCallStatus: controller.apiCallStatus,
loadingWidget: () => const Center(child: CircularProgressIndicator(),),
errorWidget: ()=> const Center(child: Text('Something went wrong!'),),
successWidget: () =>
ListView.separated(
itemCount: controller.data!.length,
separatorBuilder: (_,__) => SizedBox(height: 10.h,),
itemBuilder: (ctx,index) => ListTile(
title: Text(controller.data![index]['userId'].toString()),
subtitle: Text(controller.data![index]['title']),
),
),
);
},
)
NOTE: MyWidgetsAnimator will take care of ui changing with animation you will pass the ApiCallStatus and success,failed,loading..etc widgets and it will take care of transition
Thanks to all the amazing contributors who have helped improve this project! ๐
For support, email [email protected] or Facebook Emad Beltaje.