Jonny Borges
Committed by GitHub

Merge branch 'master' into master

... ... @@ -96,7 +96,7 @@ Get.put<S>(
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
FcBuilderFunc<S> builder,
InstanceBuilderCallback<S> builder,
)
```
... ... @@ -106,7 +106,7 @@ Get.put<S>(
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
// Example: Get.lazyPut<Controller>( () => Controller() )
FcBuilderFunc builder,
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
... ... @@ -128,7 +128,7 @@ Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
// Example: Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
FcBuilderFuncAsync<S> builder,
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
... ...
... ... @@ -99,7 +99,7 @@ Get.put<S>(
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
FcBuilderFunc<S> builder,
InstanceBuilderCallback<S> builder,
)
```
... ... @@ -109,7 +109,7 @@ Get.put<S>(
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
// Example: Get.lazyPut<Controller>( () => Controller() )
FcBuilderFunc builder,
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
... ... @@ -131,7 +131,7 @@ Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
// Example: Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
FcBuilderFuncAsync<S> builder,
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
... ...
... ... @@ -96,7 +96,7 @@ Get.put<S>(
bool permanent = false,
// opcional: permite criar a dependência usando uma função em vez da dependênia em si
FcBuilderFunc<S> builder,
InstanceBuilderCallback<S> builder,
)
```
... ... @@ -106,7 +106,7 @@ Get.put<S>(
Get.lazyPut<S>(
// obrigatório: um método que vai ser executado quando sua classe é chamada pela primeira vez
// Exemplo: "Get.lazyPut<Controller>( () => Controller()
FcBuilderFunc builder,
InstanceBuilderCallback builder,
// opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe
// precisa ser uma string única
... ... @@ -127,7 +127,7 @@ Get.lazyPut<S>(
Get.putAsync<S>(
// Obrigatório: um método assíncrono que vai ser executado para instanciar sua classe
// Exemplo: Get.putAsyn<YourAsyncClass>( () async => await YourAsyncClass() )
FcBuilderFuncAsync<S> builder,
AsyncInstanceBuilderCallback<S> builder,
// opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe
// precisa ser uma string única
... ... @@ -144,7 +144,7 @@ Get.putAsync<S>(
Get.create<S>(
// Obrigatório: Uma função que retorna uma classe que será "fabricada" toda vez que Get.find() for chamado
// Exemplo: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
InstanceBuilderCallback<S> builder,
// opcional: igual ao Get.put(), mas é usado quando você precisa de múltiplas instâncias de uma mesma classe.
// Útil caso você tenha uma lista em que cada item precise de um controller próprio
... ... @@ -170,7 +170,7 @@ A diferença fundamental entre `permanent` e `fenix` está em como você quer ar
Prosseguindo com as diferenças entre os métodos:
- Get.put e Get.putAsync seguem a mesma ordem de criação, com a diferença que o Async opta por aplicar um método assíncrono: Esses dois métodos criam e já inicializam a instância. Esta é inserida diretamente na memória, através do método interno `insert` com os parâmetros `permanent: false` e `isSingleton: true` (esse parâmetro `isSingleton` serve apenas para dizer se é para utilizar a dependência colocada em `dependency`, ou se é para usar a dependência colocada no `FcBuilderFunc`). Depois disso, é chamado o `Get.find` que imediatamente inicializa as instâncias que estão na memória.
- Get.put e Get.putAsync seguem a mesma ordem de criação, com a diferença que o Async opta por aplicar um método assíncrono: Esses dois métodos criam e já inicializam a instância. Esta é inserida diretamente na memória, através do método interno `insert` com os parâmetros `permanent: false` e `isSingleton: true` (esse parâmetro `isSingleton` serve apenas para dizer se é para utilizar a dependência colocada em `dependency`, ou se é para usar a dependência colocada no `InstanceBuilderCallback`). Depois disso, é chamado o `Get.find` que imediatamente inicializa as instâncias que estão na memória.
- Get.create: Como o nome indica, você vai "criar" a sua dependência! Similar ao `Get.put`, ela também chama o método interno `insert` para instanciamento. Contudo, `permanent` e `isSingleton` passam a ser `true` e `false` (Como estamos "criando" a nossa dependência, não tem como ela ser um Singleton de algo, logo, `false`). E por ser `permanent: true`, temos por padrão o benefício de não se perder entre telas! Além disso, não é chamado o `Get.find`, logo ela fica esperando ser chamada para ser usada. Ele é criado dessa forma para aproveitar o uso do parâmetro `permanent`, já que, vale ressaltar, o Get.create foi criado com o objetivo de criar instâncias não compartilhadas, mas que não se perdem, como por exemplo um botão em um listView, que você quer uma instância única para aquela lista - por conta disso, o Get.create deve ser usado em conjunto com o GetWidget.
... ...
import 'package:get/get.dart';
import '../data/home_model.dart';
import '../data/home_repository.dart';
class HomeController extends GetxController {
HomeController({this.homeRepository});
final HomeRepository homeRepository;
Rx<ApiModel> data = Rx<ApiModel>();
@override
void onInit() => fetchDataFromApi();
void fetchDataFromApi() async {
data.value = await homeRepository.getData();
if (data.value == null) {
Get.snackbar("Error", "Can't connect to server");
}
}
}
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'home_model.dart';
abstract class IHomeProvider {
Future<ApiModel> get();
Future<ApiModel> post(Map<String, dynamic> data);
Future<ApiModel> put(Map<String, dynamic> data);
Future<ApiModel> delete(int id);
}
class HomeProvider implements IHomeProvider {
final Dio dio;
HomeProvider({@required this.dio});
Future<ApiModel> get() async {
try {
final response = await dio.get("https://api.covid19api.com/summary");
return ApiModel.fromJson(response.data);
} catch (e) {
print(e.toString());
return null;
}
}
@override
Future<ApiModel> post(Map<String, dynamic> data) {
throw UnimplementedError();
}
@override
Future<ApiModel> put(Map<String, dynamic> data) {
throw UnimplementedError();
}
@override
Future<ApiModel> delete(int id) {
throw UnimplementedError();
}
}
import 'home_model.dart';
import 'home_provider.dart';
class HomeRepository {
HomeRepository({this.homeProvider});
final HomeProvider homeProvider;
Future<ApiModel> getData() async {
return homeProvider.get();
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_state/home/bindings/home_binding.dart';
import 'package:get_state/home/views/country_view.dart';
import 'home/views/details_view.dart';
import 'home/views/home_view.dart';
import 'package:get_state/routes/app_pages.dart';
import 'shared/logger/logger_utils.dart';
void main() {
runApp(
GetMaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
enableLog: true,
// logWriterCallback: localLogWriter,
getPages: [
GetPage(name: '/', page: () => HomePage(), binding: HomeBinding()),
GetPage(name: '/country', page: () => CountryPage()),
GetPage(name: '/details', page: () => DetailsPage()),
],
),
);
runApp(MyApp());
}
// Sample of abstract logging function
void localLogWriter(String text, {bool isError = false}) {
print('** ' + text + ' [' + isError.toString() + ']');
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
enableLog: true,
logWriterCallback: Logger.write,
initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
);
}
}
... ...
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
import '../data/home_provider.dart';
import 'package:get_state/pages/home/domain/adapters/repository_adapter.dart';
import 'package:get_state/pages/home/presentation/controllers/home_controller.dart';
import '../data/home_repository.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() {
Get.put(Dio());
Get.put(HomeProvider(dio: Get.find()));
Get.put(HomeRepository(homeProvider: Get.find()));
return HomeController(homeRepository: Get.find());
});
Get.lazyPut(() => Dio());
Get.lazyPut<IHomeRepository>(() => HomeRepository(dio: Get.find()));
Get.lazyPut(() => HomeController(homeRepository: Get.find()));
}
}
... ...
import 'package:dio/dio.dart';
import 'package:get_state/pages/home/domain/adapters/repository_adapter.dart';
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
class HomeRepository implements IHomeRepository {
HomeRepository({this.dio});
final Dio dio;
@override
Future<CasesModel> getCases() async {
try {
final response = await dio.get("https://api.covid19api.com/summary");
return CasesModel.fromJson(response.data);
} catch (e) {
print(e.toString());
return Future.error(e.toString());
}
}
}
... ...
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
abstract class IHomeRepository {
Future<CasesModel> getCases();
}
... ...
// To parse this JSON data, do
//
// final apiModel = apiModelFromJson(jsonString);
// final CasesModel = CasesModelFromJson(jsonString);
import 'dart:convert';
class ApiModel {
class CasesModel {
final Global global;
final List<Country> countries;
final String date;
ApiModel({
CasesModel({
this.global,
this.countries,
this.date,
});
factory ApiModel.fromRawJson(String str) =>
ApiModel.fromJson(json.decode(str));
factory CasesModel.fromRawJson(String str) =>
CasesModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory ApiModel.fromJson(Map<String, dynamic> json) => ApiModel(
factory CasesModel.fromJson(Map<String, dynamic> json) => CasesModel(
global: json["Global"] == null ? null : Global.fromJson(json["Global"]),
countries: json["Countries"] == null
? null
... ...
import 'package:get/get.dart';
import 'package:get_state/pages/home/domain/adapters/repository_adapter.dart';
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
enum Status { loading, success, error }
class HomeController extends GetxController {
HomeController({this.homeRepository});
/// inject repo abstraction dependency
final IHomeRepository homeRepository;
/// create a reactive status from request with initial value = loading
final status = Status.loading.obs;
/// create a reactive CasesModel. CasesModel().obs has same result
final cases = Rx<CasesModel>();
/// When the controller is initialized, make the http request
@override
void onInit() => fetchDataFromApi();
/// fetch cases from Api
Future<void> fetchDataFromApi() async {
/// When the repository returns the value, change the status to success, and fill in "cases"
return homeRepository.getCases().then(
(data) {
cases(data);
status(Status.success);
},
/// In case of error, print the error and change the status to Status.error
onError: (err) {
print("$err");
return status(Status.error);
},
);
}
}
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get_state/home/controllers/home_controller.dart';
import 'package:get_state/home/data/home_model.dart';
import 'package:get/get.dart';
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
import '../controllers/home_controller.dart';
class CountryPage extends GetWidget<HomeController> {
class CountryView extends GetWidget<HomeController> {
@override
Widget build(BuildContext context) {
return Container(
... ... @@ -28,9 +28,9 @@ class CountryPage extends GetWidget<HomeController> {
),
body: Center(
child: ListView.builder(
itemCount: controller.data.value.countries.length,
itemCount: controller.cases.value.countries.length,
itemBuilder: (context, index) {
Country country = controller.data.value.countries[index];
Country country = controller.cases.value.countries[index];
return ListTile(
onTap: () {
Get.toNamed('/details', arguments: country);
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_state/home/data/home_model.dart';
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
class DetailsPage extends StatelessWidget {
class DetailsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
Country country = Get.arguments;
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_state/home/controllers/home_controller.dart';
import 'package:get_state/pages/home/presentation/controllers/home_controller.dart';
class HomePage extends GetWidget<HomeController> {
class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return Container(
... ... @@ -23,58 +23,61 @@ class HomePage extends GetWidget<HomeController> {
centerTitle: true,
),
body: Center(
child: Obx(() => (controller.data() == null)
? CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 100,
child: Obx(
() {
Status status = controller.status.value;
if (status == Status.loading) return CircularProgressIndicator();
if (status == Status.error) return Text('Error on connection :(');
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 100,
),
Text(
"Total Confirmed",
style: TextStyle(
fontSize: 30,
),
Text(
"Total Confirmed",
style: TextStyle(
fontSize: 30,
),
),
Text(
'${controller.cases.value.global.totalConfirmed}',
style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
Text(
"Total Deaths",
style: TextStyle(
fontSize: 30,
),
Text(
'${controller.data.value.global.totalConfirmed}',
style:
TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
Text(
'${controller.cases.value.global.totalDeaths}',
style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
OutlineButton(
borderSide: BorderSide(
color: Colors.deepPurple,
width: 3,
),
SizedBox(
height: 10,
shape: StadiumBorder(),
onPressed: () {
Get.toNamed('/country');
},
child: Text(
"Fetch by country",
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
"Total Deaths",
style: TextStyle(
fontSize: 30,
),
),
Text(
'${controller.data.value.global.totalDeaths}',
style:
TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
OutlineButton(
borderSide: BorderSide(
color: Colors.deepPurple,
width: 3,
),
shape: StadiumBorder(),
onPressed: () {
Get.toNamed('/country');
},
child: Text(
"Fetch by country",
style: TextStyle(fontWeight: FontWeight.bold),
),
)
],
)),
)
],
);
},
),
),
),
);
... ...
import 'package:get/get.dart';
import 'package:get_state/pages/home/bindings/home_binding.dart';
import 'package:get_state/pages/home/presentation/views/country_view.dart';
import 'package:get_state/pages/home/presentation/views/details_view.dart';
import 'package:get_state/pages/home/presentation/views/home_view.dart';
part 'app_routes.dart';
class AppPages {
static const INITIAL = Routes.HOME;
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
),
GetPage(
name: Routes.DETAILS,
page: () => DetailsView(),
),
];
}
... ...
part of 'app_pages.dart';
abstract class Routes{
static const HOME = '/home';
static const COUNTRY = '/country';
static const DETAILS = '/details';
}
\ No newline at end of file
... ...
class Logger {
// Sample of abstract logging function
static void write(String text, {bool isError = false}) {
print('** ' + text + ' [' + isError.toString() + ']');
}
}
... ...
... ... @@ -69,4 +69,4 @@ flutter:
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
# see https://flutter.dev/custom-fonts/#from-packages
\ No newline at end of file
... ...
import 'dart:math';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:get_state/pages/home/domain/adapters/repository_adapter.dart';
import 'package:get_state/pages/home/domain/entity/cases_model.dart';
import 'package:get_state/pages/home/presentation/controllers/home_controller.dart';
import 'package:matcher/matcher.dart';
class MockReposity implements IHomeRepository {
@override
Future<CasesModel> getCases() async {
await Future.delayed(Duration(milliseconds: 100));
return Random().nextBool()
? CasesModel(
global: Global(totalDeaths: 100, totalConfirmed: 200),
)
: Future.error('error');
}
}
void main() {
final binding = BindingsBuilder(() {
Get.lazyPut<IHomeRepository>(() => MockReposity());
Get.lazyPut<HomeController>(
() => HomeController(homeRepository: Get.find()));
});
test('Test Binding', () {
expect(Get.isPrepared<HomeController>(), false);
expect(Get.isPrepared<IHomeRepository>(), false);
/// test you Binding class with BindingsBuilder
binding.builder();
expect(Get.isPrepared<HomeController>(), true);
expect(Get.isPrepared<IHomeRepository>(), true);
Get.reset();
});
test('Test Controller', () async {
/// Controller can't be on memory
expect(() => Get.find<HomeController>(), throwsA(TypeMatcher<String>()));
/// build Binding
binding.builder();
/// recover your controller
HomeController controller = Get.find();
/// check if onInit was called
expect(controller.initialized, true);
/// check initial Status
expect(controller.status.value, Status.loading);
/// await time request
await Future.delayed(Duration(milliseconds: 100));
if (controller.status.value == Status.error) {
expect(controller.cases.value, null);
}
if (controller.status.value == Status.success) {
expect(controller.cases.value.global.totalDeaths, 100);
expect(controller.cases.value.global.totalConfirmed, 200);
}
});
}
... ...
// // This is a basic Flutter widget test.
// //
// // To perform an interaction with a widget in your test, use the WidgetTester
// // utility that Flutter provides. For example, you can send tap and scroll
// // gestures. You can also use WidgetTester to find child widgets in the widget
// // tree, read text, and verify that the values of widget properties are correct.
// import 'package:flutter/material.dart';
// import 'package:flutter_test/flutter_test.dart';
// import 'package:get_state/main.dart';
// void main() {
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(MyApp());
// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
// });
// }
... ... @@ -91,4 +91,6 @@ extension Inst on GetInterface {
/// Check if a Class Instance<[S]> (or [tag]) is registered in memory.
/// - [tag] optional, if you use a [tag] to register the Instance.
bool isRegistered<S>({String tag}) => GetInstance().isRegistered<S>(tag: tag);
bool isPrepared<S>({String tag}) => GetInstance().isPrepared<S>(tag: tag);
}
... ...
... ... @@ -209,6 +209,7 @@ class GetInstance {
S find<S>({String tag}) {
String key = _getKey(S, tag);
if (isRegistered<S>(tag: tag)) {
if (_singl[key] == null) {
if (tag == null) {
throw 'Class "$S" is not register';
... ... @@ -363,6 +364,5 @@ class _InstanceBuilderFactory<S> {
class _Lazy {
bool fenix;
InstanceBuilderCallback builder;
_Lazy(this.builder, this.fenix);
}
... ...
... ... @@ -730,7 +730,7 @@ extension GetNavigation on GetInterface {
Text(
title,
style: TextStyle(
color: colorText ?? theme.iconTheme.color,
color: colorText ?? Colors.black,
fontWeight: FontWeight.w800,
fontSize: 16),
),
... ... @@ -738,7 +738,7 @@ extension GetNavigation on GetInterface {
Text(
message,
style: TextStyle(
color: colorText ?? theme.iconTheme.color,
color: colorText ?? Colors.black,
fontWeight: FontWeight.w300,
fontSize: 14),
),
... ...
... ... @@ -19,7 +19,7 @@ abstract class Bindings {
/// ````
class BindingsBuilder extends Bindings {
/// Register your dependencies in the [builder] callback.
final Function() builder;
final void Function() builder;
BindingsBuilder(this.builder);
... ...
... ... @@ -44,9 +44,13 @@ abstract class DisposableInterface {
/// It uses an internal "callable" type, to avoid any @overrides in subclases.
/// This method should be internal and is required to define the lifetime cycle
/// of the subclass.
///
final onStart = _InternalFinalCallback<void>();
bool _initialized = false;
/// Checks whether the controller has already been initialized.
bool get initialized => _initialized;
DisposableInterface() {
onStart.callback = _onStart;
}
... ... @@ -54,6 +58,7 @@ abstract class DisposableInterface {
// Internal callback that starts the cycle of this controller.
void _onStart() {
onInit();
_initialized = true;
SchedulerBinding.instance?.addPostFrameCallback((_) => onReady());
}
... ...