What’s Getx?
Getx is a stable and powerful library for Flutter that can we use for
- State Management
- Route Management
- Dependency Injection
How to install
Add Get to your pubspec.yaml file
dependencies:
get:
Import get.dart in files that will be used
import 'package:get/get.dart';
in main.dart, use GetMaterialApp instead of having MaterialApp.
void main() => runApp(GetMaterialApp(home: HomeView()));
Why we should use Getx?
Getx focused on Performance, Code-Organization, and Plain Syntax.
Let's explain each one.
Performance
Getx emphasized performance and clean architecture and compared to other libraries like Bloc, Provider, etc, in my opinion, and analysis, Getx is on top. in the research, Getx used less memory than other libraries
Code-Organization
Getx has decoupled View, Controller, Dependency Injection, and Navigation layers. It means that the code you write is more readable and everything is on its layer.
It’s just an image to describe the organization, don’t be afraid, I’ll explain later what they are and why we use them 😀.
Plain Syntax
Getx has a straightforward syntax. Just like above, to get a repository from dependency injection you just write one line code.
Get.find<HomeRepository>();
Getx Features
Let’s talk about some features that Getx gives us.
State Management
First of all, let’s talk about what is State in Flutter is. In every view, we have some widgets that will be updated over time. So the information that changes every time and needs to be kept and updated in UI, we call it State.
Getx provides two types of State Management
- Simple State Management
- Reactive State Management
So what they are? Stay with me and see
Simple State Management
In this example, we have a simple string variable in our controller. Whenever the changeText() function is called, we want to update our widget that is listening to the string variable by using GetBuilder. If we don’t call the update() function, the widget won’t be updated because the GetBuilder widget waits for it and when we don’t call it, it won’t work though.
Update the Specific GetBuilder widget
In addition to builder callback, GetBuilder also has an id argument. If you want to update the widget with a unique id, you pass the id through the update() function and it only updates the GetBuilder widget with given the id(note that update() function takes a List of Ids). I’ll show you an example.
In our view, we have two widgets with different Ids and in the controller, we have a function that updates the text’s value but we only update one of them in the widget state. In the example, we call the update() function with a List of Ids that is the only id of the GetBuilder named ‘first’ that will be updated in the UI.
Reactive State Management
So what did we do in the above sample? First of all, we have a view called HomeView that extends StatelessWidget(To clean code and best practice, use Getx class to build views. Like extends GetView or GetResponsiveView I’’ be explain later). In the body argument, we got the Obx widget. What does it use for? So in the Obx callback, we must return a widget that used an Rx variable(if you won’t, you’ll get an exception). e.g. we just want to display the Rx bool value in the Text widget. Initially return your Text widget in Obx callback and boom, That’s it. Whenever you change your value, Widget Automatically gets updated, and don’t need to call any function to update the UI like what we do in Simple State Management.
What is the difference between GetView and GetResponsiveView?
That’s obvious. You can guess it by looking at their names. The difference is, in GetResponsiveView you have access to a variable called screen that gives you a ResponsiveScreen instance and you can manage your design per device.
Dependency Injection
Getx has a simple dependency injection. With single-line code, you can inject dependency and on the opposite, you can get the dependency as well. The injection can be done by different methods, I’ll explain two of them:
Get.put<DataType>(value)
Get.lazyPut<DataType>(() => value);
What is their difference? (Official Document)
- Get.put: Injects an Instance<DataType> in memory
- Get.lazyPut: Create a new Instance<DataType> lazily from the builder callback. The first time you call Get.find, the builder callback will create the Instance and persist as a Singleton (like you would use Get.put).
If you are using Get’s State Manager, pay more attention to the bindings API, which will make it easier to connect your view to your controller.
Bindings
In Getx we have a Bindings class for each view. What does it do? so whenever you created a binding class, all the dependencies that you inject, All services that you want to start when View is created, or even your controller, will bind to your view. What’s that mean? It means that they will create and destroy depending on your view’s lifecycle.
thus you create your binding class, let’s bind it to the preferred view.
you can attach your binding class in different ways
Binding in Named Routes
Just pass the binding class to GetPage’s binding argument
GetPage(
name: "/",
page: () => HomeView(),
binding: HomeBinding(),
)
if you don’t want to create a binding class, you can use BindingsBuilder callback like below
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut(() => HomeViewRepository());
Get.lazyPut(() => HomeViewController(repository: Get.find<HomeViewRepository>()));
}),
),
Binding in Normal Routes
Just Pass The binding class or BindingsBuilder callback through the Get.to function
Get.to(HomeView(), binding: HomeBinding());
Route Management
if you want to get rid of context for navigation to a specific route or show some dialog, snackbar, toast, etc, Getx is the best thing you choose.
Navigate To The Unnamed Routes
To navigate to a new screen
Get.to(HomeView());
To close everything you’ve opened like the screen, snackbar, dialog, etc
Get.back();
To navigate to a new screen and have no option to return (Splash or Auth screen)
Get.off(HomeView());
To navigate to a new screen and cancel all previous routes(e.g. Splash -> Login -> HomeView. all previous routes will be deleted from the stack)
Get.offAll(HomeView());
To navigate to the new screen and do something base on what you return as a result
var result = await Get.to(ProductView());
on the new screen, send back the result data
Get.back(result: ResultEnum.AddToCart);
To navigate to the new screen and send an argument (arguments take a dynamic value)
Get.to(HomeView(), arguments: true);
on the next screen, you can get arguments and do some functionality base on your arguments
var args = Get.arguments;
Navigation To The Named Route
First of all, To create a named route, for each screen you want to have, you must create a List of GetPage class. each screen mu have a GetPage instance and assign it to the getPages parameter into GetMaterialApp widget that you create in main.dart.
runApp(
GetMaterialApp(
initialRoute: '/splash',
unknownRoute: GetPage(name: '/404', page: () => NotFoundView()),
getPages: [
GetPage(name: '/splash', page: () => SplashView()),
GetPage(name: '/', page: () => HomeView()),
GetPage(name: '/product', page: () => ProductView()),
],
)
);
initialRoute: defines the first page of the application
unknownRoute: To handle non-defined routes (404 NotFound View)
getPages: defines the pages and screens that our application has.
What is GetPage
GetPage is a class that defines a screen for each route we are navigating.
Principal GetPage’s arguments
name: defines a route for the page
page: a callback method for initializing the screen view
binding: attach the binding class to the page
bindings: attach a list of binding classes to the page
middlewares: attach a list of GetMiddleware to the page
Navigation
To navigate and send data to a new screen
Get.toNamed('/product', argument: 136);
on the new screen, you can get the arguments
var args = Get.arguments
also, we have offNamed, offAllNamed, etc, like Unnamed Routes.
Dynamic URLs
Getx has handled the Dynamic URLs in their packages. You can pass the parameters through the routes and use them on another screen.
Get.offNamed('/product?id=1381');
on another screen
var id = Get.parameters['id'];
Named Parameters
In GetPage you can define Named Parameters and use it as below
GetPage(
name: '/product/',
page: () => ProductView()
),
GetPage(
name: '/product/:id',
page: () => ProductView()
),
In the above example, first, we define the page without named parameters(note that you must set the / at the end of the route), and in the second object, we defined it.
Passing data in Named parameters
Get.offNamed('/product/1381');
on the other screen
var id = Get.parameters['id'];
Dynamic URLs And NamedParameters Mixture
You can pass the data in both ways. But if you want to mix them and use their approaches together.
Passing Data
Get.offNamed('/product/1381?flag=true');
// OR
var parameters = <String, String>{"flag": "true"};
Get.offNamed('/product/1381', parameters: parameters);
on the other screen
var id = Get.parameters['id'];
var flag = Get.parameters['flag'];
Middlewares
To protect your screens with some functionality, you can use middleware classes. First, create a class that extends GetMiddleware and override the redirect method and implement the functionality
class AuthMiddleWare extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
if (LocalStorage.userInfo() == null) {
return RouteSettings(name: '/auth');
}
return null;
}
}
In the above example, we declare that if the user is not logged in and there is no information about them in Local Storage, then, we redirect them to the auth screen.
This is how you can set middleware to pages
GetPage(
name: '/product',
page: () => ProductView(),
middlewares: [AuthMiddleWare()],
),
each page taking a list of middleware in order to separate middleware.
SnackBar
Showing the snackbar, dialog, bottomsheet is so easy with Getx.
You can show it anywhere without any context with a single-line code
Get.snackbar(
'Title',
'Message',
);
Dialog
You just pass the AlertDialog widget through the Getx
Get.dialog(
AlertDialog(),
);
BottomSheet
You just need to pass the widget through the Getx
Get.bottomSheet(
Container(),
);
Utils
Getx also provides some utilities. For instance, Internationalization, and HTTP requests to communicate between your app and back-end.
Internationalization
Translations will be handled by a key-value map. You just need to create a class that extends Translations
class CustomTranslator extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'Hello': 'Hello Dear',
},
'fa_IR': {
'Hello': 'سلام عزیز',
}
};
}
and add the class to your GetMaterialApp
void main() => runApp(
GetMaterialApp(
home: HomeView(),
locale: Locale('en', 'US'),
fallbackLocale: Locale('fa', 'IR'),
translations: CustomTranslator(),
)
);
You can set a default locale to your app in order to translate a default language like en_US. specifying the fallback locale in case an invalid locale is selected. if you want to use the user’s locale you can call Get.deviceLocale.(That’s why we use the fallback locale. If the user locale is invalid in our app in case Get wll use the fallback locale)
Change Locale Manually
When we go further, we want the user to choose the locale and update it
var locale = Locale('fa', 'IR');
Get.updateLocale(locale);
How to use in Screens
Just append ‘ .tr ’ to the specified key and it’ll be translated
Text(
'Hello'.tr, // out put will be the translated text.
)
Translate with Parameters
You can pass parameters to the translation and it’ll put in the place
Map<String, Map<String, String>> get keys => {
'en_US': {
'Hello_Message': 'Hello @firstName , @lastName',
},
'es_ES': {
'Hello_Message': 'سلام @firstName , @lastName',
}
};
And pass the parameter
Text(
'Hello_Message'.trParams({
'firstName': 'Amirsalar',
'lastName': 'Salehi'
})
)
GetConnect
You can easily create a class that extends GetConnect Class and implement POST/GET/DELETE/PUT/SOCKET methods.
class RemoteProvider extends GetConnect {
Future<Response> getProduct(int id) => get('https://api.com/product?id=$id');
// OR
Future<Response> getProduct(int id) => get('https://api.com/product', query: {'id': id});
Future<Response> deleteOfferFactor(int id) => delete('https://api.com/product', query: {'id': id});
Future<Response> createProduct(dynamic data) => post('https://api.com/product', data);
Future<Response> updateProduct(dynamic data) => put('https://api.com/product', data);
GetSocket userProfile() => socket('https://api.com/users/socket');
}
Customize Configuration
You can customize your GetConnect client. you are able to add RequestModifier or ResponseModifier to listen to requests and responses and make changes to them or set a base URL or even the number of attempts and etc.
class RemoteProvider extends GetConnect {
@override
void onInit() {
httpClient.baseUrl = 'https://api.com';
// It's will attach Authorization for each request
httpClient.addRequestModifier((request) {
request.headers['Authorization'] = 'Bearer $token';
return request;
});
// (OFFICIAL DOCUMENT)
// Even if the server sends data from the country "Brazil",
// it will never be displayed to users, because you remove
// that data from the response, even before the response is delivered
httpClient.addResponseModifier<CasesModel>((request, response) {
CasesModel model = response.body;
if (model.countries.contains('Brazil')) {
model.countries.remove('Brazilll');
}
});
httpClient.addAuthenticator((request) async {
final response = await get("http://api.com/refreshToken");
final token = response.body['accessToken'];
// Set the header
request.headers['Authorization'] = "Bearer $token";
return request;
});
// addAuthenticator will be called 3 times
httpClient.maxAuthRetries = 3;
}
}
}
Conclusion
Getx is the best solution in order to use Smart StateManagement, DependencyInjection, and RouteManagement, and save time in coding. I hardly recommend you to use this library. If you want full documentation, see the Official Document.
With Respect, Amirsalar Salehi(Lorevantonio).