Jonatas

added example

  1 +# GetX codelab
  2 +
  3 +In this example you will learn the basics of GetX. You will see how much easier it is to code with this framework, and you will know what problems GetX proposes to solve.
  4 +
  5 +If the default Flutter application were rewritten with Getx, it would have only a few lines of code. The Getx state manager is easier than using setState. You just need to add a ".obs" at the end of your variable, and wrap the widget you want to change within a Obx().
  6 +
  7 +```dart
  8 +void main() => runApp(MaterialApp(home: Home()));
  9 +
  10 +class Home extends StatelessWidget {
  11 + var count = 0.obs;
  12 + @override
  13 + Widget build(context) => Scaffold(
  14 + appBar: AppBar(title: Text("counter")),
  15 + body: Center(
  16 + child: Obx(() => Text("$count")),
  17 + ),
  18 + floatingActionButton: FloatingActionButton(
  19 + child: Icon(Icons.add),
  20 + onPressed: () => count ++,
  21 + ));
  22 +}
  23 +```
  24 +However, this simple example deals with ephemeral state management. If you need to access this data in several places in your application, you will need global state management.
  25 +The most common way to do this is to separate the business logic from its visualization. I know, you've heard this concept before, and it might have been scary for you, but with Getx, it's stupidly simple.
  26 +Getx provides you with a class capable of initializing data, and removing it when it is no longer needed, and its use is very simple:
  27 +Just create a class by extending GetxController and insert ALL your variables and functions there.
  28 +
  29 +```dart
  30 +class Controller extends GetxController {
  31 + var count = 0;
  32 + void increment() {
  33 + count++;
  34 + update();
  35 + }
  36 +}
  37 +```
  38 +Here you can choose between simple state management, or reactive state management.
  39 +The simple one will update its variable on the screen whenever update() is called. It is used with a widget called "GetBuilder".
  40 +
  41 +```dart
  42 +class Home extends StatelessWidget {
  43 + final controller = Get.put(Controller());
  44 + @override
  45 + Widget build(BuildContext context) {
  46 + return Scaffold(
  47 + appBar: AppBar(title: Text("counter")),
  48 + body: Center(
  49 + child: Column(
  50 + mainAxisAlignment: MainAxisAlignment.center,
  51 + children: [
  52 + GetBuilder<Controller>(
  53 + builder: (_) => Text(
  54 + 'clicks: ${controller.count}',
  55 + )),
  56 + RaisedButton(
  57 + child: Text('Next Route'),
  58 + onPressed: () {
  59 + Get.to(Second());
  60 + },
  61 + ),
  62 + ],
  63 + ),
  64 + ),
  65 + floatingActionButton: FloatingActionButton(
  66 + child: Icon(Icons.add),
  67 + onPressed: controller.increment(),
  68 + ),
  69 + );
  70 + }
  71 +}
  72 +class Second extends StatelessWidget {
  73 + final Controller ctrl = Get.find();
  74 + @override
  75 + Widget build(context){
  76 + return Scaffold(body: Center(child: Text("${ctrl.count}")));
  77 + }
  78 +}
  79 +```
  80 +When instantiating your controller, you may have noticed that we use `Get.put(Controller())`. This is enough to make your controller available to other pages as long as it is in memory.
  81 +You may have noticed that you have a new button using `Get.to(Second())`. This is enough to navigate to another page. You don't need a
  82 +
  83 +```dart
  84 +Navigator.of(context).push(context,
  85 + MaterialPageRoute(context, builder: (context){
  86 + return Second();
  87 + },
  88 +);
  89 +```
  90 +all that huge code, it's reduced to a simple `Get.to(Second())`. Isn't that incredible?
  91 +
  92 +You may also have noticed that on the other page you simply used Get.find (), and the framework knows which controller you registered previously, and returns it effortlessly, you don't need to type the find with `Get.find<Controller>()` so that he knows what you need.
  93 +
  94 +However, this is simple state management. You may want to control the output of each change of state, you may want to use and manipulate streams, you may want a variable to only be changed on the screen, when it definitely changes, you may need a debounce on changing the state, and to these and other needs, Getx has powerful, intelligent, and lightweight state management that can address any need, regardless of the size of your project. And its use is even easier than the previous one.
  95 +
  96 +In your controller, you can remove the update method, Getx is reactive, so when a variable changes, it will automatically change on the screen.
  97 +You just need to add a ".obs" in front of your variable, and that's it, it's already reactive.
  98 +
  99 +```dart
  100 +class Controller extends GetxController {
  101 + var count = 0.obs;
  102 + void increment() {
  103 + count++;
  104 + }
  105 +}
  106 +```
  107 +Now you just need to change GetBuilder for GetX and that's it
  108 +```dart
  109 +class Home extends StatelessWidget {
  110 + final controller = Get.put(Controller());
  111 + @override
  112 + Widget build(BuildContext context) {
  113 + return Scaffold(
  114 + appBar: AppBar(title: Text("counter")),
  115 + body: Center(
  116 + child: Column(
  117 + mainAxisAlignment: MainAxisAlignment.center,
  118 + children: [
  119 + GetX<Controller>(
  120 + builder: (_) => Text(
  121 + 'clicks: ${controller.count}',
  122 + )),
  123 + RaisedButton(
  124 + child: Text('Next Route'),
  125 + onPressed: () {
  126 + Get.to(Second());
  127 + },
  128 + ),
  129 + ],
  130 + ),
  131 + ),
  132 + floatingActionButton: FloatingActionButton(
  133 + child: Icon(Icons.add),
  134 + onPressed: controller.increment(),
  135 + ),
  136 + );
  137 + }
  138 +}
  139 +class Second extends StatelessWidget {
  140 + final Controller ctrl = Get.find();
  141 + @override
  142 + Widget build(context){
  143 + return Scaffold(body: Center(child: Text("${ctrl.count}")));
  144 + }
  145 +}
  146 +```
  147 +
  148 +GetX is a useful widget when you want to inject the controller into the init property, or when you want to retrieve an instance of the controller within the widget itself. In other cases, you can insert your widget into an Obx, which receives a widget function. This looks much easier and clearer, just like the first example
  149 +
  150 +```dart
  151 +class Home extends StatelessWidget {
  152 + final controller = Get.put(Controller());
  153 + @override
  154 + Widget build(BuildContext context) {
  155 + return Scaffold(
  156 + appBar: AppBar(title: Text("counter")),
  157 + body: Center(
  158 + child: Column(
  159 + mainAxisAlignment: MainAxisAlignment.center,
  160 + children: [
  161 + Obx(() => Text(
  162 + 'clicks: ${controller.count}',
  163 + )),
  164 + RaisedButton(
  165 + child: Text('Next Route'),
  166 + onPressed: () {
  167 + Get.to(Second());
  168 + },
  169 + ),
  170 + ],
  171 + ),
  172 + ),
  173 + floatingActionButton: FloatingActionButton(
  174 + child: Icon(Icons.add),
  175 + onPressed: controller.increment(),
  176 + ),
  177 + );
  178 + }
  179 +}
  180 +class Second extends StatelessWidget {
  181 + final Controller ctrl = Get.find();
  182 + @override
  183 + Widget build(context){
  184 + return Scaffold(body: Center(child: Text("${ctrl.count}")));
  185 + }
  186 +}
  187 +```
  188 +If you are a more demanding user, you must have said: BLoC separates the View from the business logic. But what about the presentation logic? Will I be obliged to attach it to the visualization? Will I be totally dependent on the context for everything I want to do? Will I have to insert a bunch of variables, TextEditingControllers in my view? If I need to hear the Scroll on my screen, do I need to insert an initState and a function into my view? If I need to trigger an action when this scroll reaches the end, do I insert it into the view, or in the bloc/changeNotifier class? Well, these are common architectural questions, and most of the time the solution to them is ugly.
  189 +With Getx you have no doubts when your architecture, if you need a function, it must be on your Controller, if you need to trigger an event, it needs to be on your controller, your view is generally a StatelessWidget free from any dirt.
  190 +This means that if someone has already done something you’re looking for, you can copy the controller entirely from someone else, and it will work for you, this level of standardization is what Flutter lacked, and it’s because of this that Getx has become so popular in the last few days. Flutter is amazing, and has minor one-off problems. Getx came to solve these specific problems. Flutter provides powerful APIs, and we turn them into an easy, clean, clear, and concise API for you to build applications in a fast, performance and highly scalable way.
  191 +If you have already asked yourself some of these questions above, you have certainly found the solution to your problems. Getx is able to completely separate any logic, be it presentation or business, and you will only have pure widgets in your visualization layer. No variables, no functions, just widgets. This will facilitate the work of your team working with the UI, as well as your team working with your logic. They won't depend on initState to do anything, their controller has onInit. Your code can be tested in isolation, the way it is.
  192 +But what about dependency injection? Will I have it attached to my visualization?
  193 +If you've used any state manager, you've probably heard of "multiAnything", or something like that.
  194 +You have probably already inserted dozens of ChangeNotifier or Blocs classes in a widget, just to have it all over the tree.
  195 +This level of coupling is yet another problem that Getx came to solve. For this, in this ecosystem we use BINDINGS.
  196 +Bindings are dependency injection classes. They are completely outside your widget tree, making your code cleaner, more organized, and allowing you to access it anywhere without context.
  197 +You can initialize dozens of controllers in your Bindings, when you need to know what is being injected into your view, just open the Bindings file on your page and that's it, you can clearly see what has been prepared to be initialized in your View.
  198 +Bindings is the first step towards having a scalable application, you can visualize what will be injected into your page, and decouple the dependency injection from your visualization layer.
  199 +
  200 +To create a Binding, simply create a class and implement Bindings
  201 +
  202 +```dart
  203 +class SampleBind extends Bindings {
  204 + @override
  205 + void dependencies() {
  206 + Get.lazyPut<Controller>(() => Controller());
  207 + Get.lazyPut<Controller2>(() => Controller2());
  208 + Get.lazyPut<Controller3>(() => Controller3());
  209 + }
  210 +}
  211 +```
  212 +You can use with named routes (preferred)
  213 +```dart
  214 +void main() {
  215 + runApp(GetMaterialApp(
  216 + initialRoute: '/home',
  217 + getPages: [
  218 + GetPage(name: '/home', page: () => First(), binding: SampleBind()),
  219 + ],
  220 + ));
  221 +}
  222 +```
  223 +
  224 +Or unnamed
  225 +```dart
  226 +Get.to(Second(), binding: SampleBind());
  227 +```
  228 +
  229 +There is a trick that can clear your View even more.
  230 +Instead of extending StatelessWidget, you can extend GetView, which is a StatelessWidget with a "controller" property.
  231 +
  232 +See the example and see how clean your code can be using this approach.
  233 +The standard Flutter counter has almost 100 lines, it would be summarized to:
  234 +
  235 +on main.dart
  236 +```dart
  237 +void main() {
  238 + runApp(GetMaterialApp(
  239 + initialRoute: '/home',
  240 + getPages: [
  241 + GetPage(name: '/home', page: () => HomeView(), binding: HomeBinding()),
  242 + ],
  243 + ));
  244 +}
  245 +```
  246 +on home_bindings.dart
  247 +```dart
  248 +class HomeBinding implements Bindings {
  249 + @override
  250 + void dependencies() {
  251 + Get.lazyPut(() => HomeController());
  252 + }
  253 +}
  254 +```
  255 +
  256 +on home_controller.dart
  257 +```dart
  258 +class HomeController extends GetxController {
  259 + var count = 0.obs;
  260 + void increment() => count++;
  261 +}
  262 +```
  263 +on home_view.dart
  264 +```dart
  265 +class Home extends GetView<Controller> {
  266 + @override
  267 + Widget build(context) => Scaffold(
  268 + appBar: AppBar(title: Text("counter")),
  269 + body: Center(
  270 + child: Obx(() => Text("${controller.counter}")),
  271 + ),
  272 + floatingActionButton: FloatingActionButton(
  273 + child: Icon(Icons.add),
  274 + onPressed: controller.increment,
  275 + ));
  276 +}
  277 +```
  278 +What did you do:
  279 +He built an example of the counter, (with less code than the original), decoupling its visualization, its business logic, its dependency injection, in a clean, scalable way, facilitating code maintenance and reusability. If you need an accountant on another project, or your developer friend does, you can just share the content of the controller file with him, and everything will work perfectly.
  280 +As the view has only widgets, you can use a view for android, and another for iOS, taking advantage of 100% of your business logic, your view has only widgets! you can change them however you want, without affecting your application in any way.
  281 +
  282 +However, some examples like internationalization, Snackbars without context, validators, responsiveness and other Getx resources, were not explored (and it would not even be possible to explore all resources in such a simple example), so below is an example not very complete, but trying demonstrate how to use internationalization, reactive custom classes, reactive lists, snackbars contextless, workers etc.
  283 +
  284 +```dart
  285 +import 'package:flutter/material.dart';
  286 +import 'package:get/get.dart';
  287 +
  288 +void main() {
  289 + runApp(GetMaterialApp(
  290 + // It is not mandatory to use named routes, but dynamic urls are interesting.
  291 + initialRoute: '/home',
  292 + defaultTransition: Transition.native,
  293 + translations: MyTranslations(),
  294 + locale: Locale('pt', 'BR'),
  295 + getPages: [
  296 + //Simple GetPage
  297 + GetPage(name: '/home', page: () => First()),
  298 + // GetPage with custom transitions and bindings
  299 + GetPage(
  300 + name: '/second',
  301 + page: () => Second(),
  302 + customTransition: SizeTransitions(),
  303 + binding: SampleBind(),
  304 + ),
  305 + // GetPage with default transitions
  306 + GetPage(
  307 + name: '/third',
  308 + transition: Transition.cupertino,
  309 + page: () => Third(),
  310 + ),
  311 + ],
  312 + ));
  313 +}
  314 +
  315 +class MyTranslations extends Translations {
  316 + @override
  317 + Map<String, Map<String, String>> get keys => {
  318 + 'en': {
  319 + 'title': 'Hello World %s',
  320 + },
  321 + 'en_US': {
  322 + 'title': 'Hello World from US',
  323 + },
  324 + 'pt': {
  325 + 'title': 'Olá de Portugal',
  326 + },
  327 + 'pt_BR': {
  328 + 'title': 'Olá do Brasil',
  329 + },
  330 + };
  331 +}
  332 +
  333 +class Controller extends GetxController {
  334 + int count = 0;
  335 + void increment() {
  336 + count++;
  337 + // use update method to update all count variables
  338 + update();
  339 + }
  340 +}
  341 +
  342 +class First extends StatelessWidget {
  343 + @override
  344 + Widget build(BuildContext context) {
  345 + return Scaffold(
  346 + appBar: AppBar(
  347 + leading: IconButton(
  348 + icon: Icon(Icons.add),
  349 + onPressed: () {
  350 + Get.snackbar("Hi", "I'm modern snackbar");
  351 + },
  352 + ),
  353 + title: Text("title".trArgs(['John'])),
  354 + ),
  355 + body: Center(
  356 + child: Column(
  357 + mainAxisAlignment: MainAxisAlignment.center,
  358 + children: [
  359 + GetBuilder<Controller>(
  360 + init: Controller(),
  361 + // You can initialize your controller here the first time. Don't use init in your other GetBuilders of same controller
  362 + builder: (_) => Text(
  363 + 'clicks: ${_.count}',
  364 + )),
  365 + RaisedButton(
  366 + child: Text('Next Route'),
  367 + onPressed: () {
  368 + Get.toNamed('/second');
  369 + },
  370 + ),
  371 + RaisedButton(
  372 + child: Text('Change locale to English'),
  373 + onPressed: () {
  374 + Get.updateLocale(Locale('en', 'UK'));
  375 + },
  376 + ),
  377 + ],
  378 + ),
  379 + ),
  380 + floatingActionButton: FloatingActionButton(
  381 + child: Icon(Icons.add),
  382 + onPressed: () {
  383 + Get.find<Controller>().increment();
  384 + }),
  385 + );
  386 + }
  387 +}
  388 +
  389 +class Second extends GetView<ControllerX> {
  390 + @override
  391 + Widget build(BuildContext context) {
  392 + return Scaffold(
  393 + appBar: AppBar(
  394 + title: Text('second Route'),
  395 + ),
  396 + body: Center(
  397 + child: Column(
  398 + mainAxisAlignment: MainAxisAlignment.center,
  399 + children: [
  400 + GetX<ControllerX>(
  401 + // Using bindings you don't need of init: method
  402 + // Using Getx you can take controller instance of "builder: (_)"
  403 + builder: (_) {
  404 + print("count1 rebuild");
  405 + return Text('${_.count1}');
  406 + },
  407 + ),
  408 + GetX<ControllerX>(
  409 + builder: (_) {
  410 + print("count2 rebuild");
  411 + return Text('${controller.count2}');
  412 + },
  413 + ),
  414 + GetX<ControllerX>(builder: (_) {
  415 + print("sum rebuild");
  416 + return Text('${_.sum}');
  417 + }),
  418 + GetX<ControllerX>(
  419 + builder: (_) => Text('Name: ${controller.user.value.name}'),
  420 + ),
  421 + GetX<ControllerX>(
  422 + builder: (_) => Text('Age: ${_.user.value.age}'),
  423 + ),
  424 + RaisedButton(
  425 + child: Text("Go to last page"),
  426 + onPressed: () {
  427 + Get.toNamed('/third', arguments: 'arguments of second');
  428 + },
  429 + ),
  430 + RaisedButton(
  431 + child: Text("Back page and open snackbar"),
  432 + onPressed: () {
  433 + Get.back();
  434 + Get.snackbar(
  435 + 'User 123',
  436 + 'Successfully created',
  437 + );
  438 + },
  439 + ),
  440 + RaisedButton(
  441 + child: Text("Increment"),
  442 + onPressed: () {
  443 + Get.find<ControllerX>().increment();
  444 + },
  445 + ),
  446 + RaisedButton(
  447 + child: Text("Increment"),
  448 + onPressed: () {
  449 + Get.find<ControllerX>().increment2();
  450 + },
  451 + ),
  452 + RaisedButton(
  453 + child: Text("Update name"),
  454 + onPressed: () {
  455 + Get.find<ControllerX>().updateUser();
  456 + },
  457 + ),
  458 + RaisedButton(
  459 + child: Text("Dispose worker"),
  460 + onPressed: () {
  461 + Get.find<ControllerX>().disposeWorker();
  462 + },
  463 + ),
  464 + ],
  465 + ),
  466 + ),
  467 + );
  468 + }
  469 +}
  470 +
  471 +class Third extends GetView<ControllerX> {
  472 + @override
  473 + Widget build(BuildContext context) {
  474 + return Scaffold(
  475 + floatingActionButton: FloatingActionButton(onPressed: () {
  476 + controller.incrementList();
  477 + }),
  478 + appBar: AppBar(
  479 + title: Text("Third ${Get.arguments}"),
  480 + ),
  481 + body: Center(
  482 + child: Obx(() => ListView.builder(
  483 + itemCount: controller.list.length,
  484 + itemBuilder: (context, index) {
  485 + return Text("${controller.list[index]}");
  486 + }))),
  487 + );
  488 + }
  489 +}
  490 +
  491 +class SampleBind extends Bindings {
  492 + @override
  493 + void dependencies() {
  494 + Get.lazyPut<ControllerX>(() => ControllerX());
  495 + }
  496 +}
  497 +
  498 +class User {
  499 + User({this.name = 'Name', this.age = 0});
  500 + String name;
  501 + int age;
  502 +}
  503 +
  504 +class ControllerX extends GetxController {
  505 + final count1 = 0.obs;
  506 + final count2 = 0.obs;
  507 + final list = [56].obs;
  508 + final user = User().obs;
  509 +
  510 + updateUser() {
  511 + user.update((value) {
  512 + value.name = 'Jose';
  513 + value.age = 30;
  514 + });
  515 + }
  516 +
  517 + /// Once the controller has entered memory, onInit will be called.
  518 + /// It is preferable to use onInit instead of class constructors or initState method.
  519 + /// Use onInit to trigger initial events like API searches, listeners registration
  520 + /// or Workers registration.
  521 + /// Workers are event handlers, they do not modify the final result,
  522 + /// but it allows you to listen to an event and trigger customized actions.
  523 + /// Here is an outline of how you can use them:
  524 +
  525 + /// made this if you need cancel you worker
  526 + Worker _ever;
  527 +
  528 + @override
  529 + onInit() {
  530 + /// Called every time the variable $_ is changed
  531 + _ever = ever(count1, (_) => print("$_ has been changed (ever)"));
  532 +
  533 + everAll([count1, count2], (_) => print("$_ has been changed (everAll)"));
  534 +
  535 + /// Called first time the variable $_ is changed
  536 + once(count1, (_) => print("$_ was changed once (once)"));
  537 +
  538 + /// Anti DDos - Called every time the user stops typing for 1 second, for example.
  539 + debounce(count1, (_) => print("debouce$_ (debounce)"),
  540 + time: Duration(seconds: 1));
  541 +
  542 + /// Ignore all changes within 1 second.
  543 + interval(count1, (_) => print("interval $_ (interval)"),
  544 + time: Duration(seconds: 1));
  545 + }
  546 +
  547 + int get sum => count1.value + count2.value;
  548 +
  549 + increment() => count1.value++;
  550 +
  551 + increment2() => count2.value++;
  552 +
  553 + disposeWorker() {
  554 + _ever.dispose();
  555 + // or _ever();
  556 + }
  557 +
  558 + incrementList() => list.add(75);
  559 +}
  560 +
  561 +class SizeTransitions extends CustomTransition {
  562 + @override
  563 + Widget buildTransition(
  564 + BuildContext context,
  565 + Curve curve,
  566 + Alignment alignment,
  567 + Animation<double> animation,
  568 + Animation<double> secondaryAnimation,
  569 + Widget child) {
  570 + return Align(
  571 + alignment: Alignment.center,
  572 + child: SizeTransition(
  573 + sizeFactor: CurvedAnimation(
  574 + parent: animation,
  575 + curve: curve,
  576 + ),
  577 + child: child,
  578 + ),
  579 + );
  580 + }
  581 +}
  582 +```
  1 +flutter/ephemeral
  1 +cmake_minimum_required(VERSION 3.10)
  2 +project(runner LANGUAGES CXX)
  3 +
  4 +set(BINARY_NAME "example")
  5 +set(APPLICATION_ID "com.example.example")
  6 +
  7 +cmake_policy(SET CMP0063 NEW)
  8 +
  9 +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
  10 +
  11 +# Configure build options.
  12 +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  13 + set(CMAKE_BUILD_TYPE "Debug" CACHE
  14 + STRING "Flutter build mode" FORCE)
  15 + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
  16 + "Debug" "Profile" "Release")
  17 +endif()
  18 +
  19 +# Compilation settings that should be applied to most targets.
  20 +function(APPLY_STANDARD_SETTINGS TARGET)
  21 + target_compile_features(${TARGET} PUBLIC cxx_std_14)
  22 + target_compile_options(${TARGET} PRIVATE -Wall -Werror)
  23 + target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
  24 + target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
  25 +endfunction()
  26 +
  27 +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
  28 +
  29 +# Flutter library and tool build rules.
  30 +add_subdirectory(${FLUTTER_MANAGED_DIR})
  31 +
  32 +# System-level dependencies.
  33 +find_package(PkgConfig REQUIRED)
  34 +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
  35 +
  36 +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
  37 +
  38 +# Application build
  39 +add_executable(${BINARY_NAME}
  40 + "main.cc"
  41 + "my_application.cc"
  42 + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
  43 +)
  44 +apply_standard_settings(${BINARY_NAME})
  45 +target_link_libraries(${BINARY_NAME} PRIVATE flutter)
  46 +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
  47 +add_dependencies(${BINARY_NAME} flutter_assemble)
  48 +
  49 +# Generated plugin build rules, which manage building the plugins and adding
  50 +# them to the application.
  51 +include(flutter/generated_plugins.cmake)
  52 +
  53 +
  54 +# === Installation ===
  55 +# By default, "installing" just makes a relocatable bundle in the build
  56 +# directory.
  57 +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
  58 +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  59 + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
  60 +endif()
  61 +
  62 +# Start with a clean build bundle directory every time.
  63 +install(CODE "
  64 + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
  65 + " COMPONENT Runtime)
  66 +
  67 +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
  68 +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
  69 +
  70 +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
  71 + COMPONENT Runtime)
  72 +
  73 +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
  74 + COMPONENT Runtime)
  75 +
  76 +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
  77 + COMPONENT Runtime)
  78 +
  79 +if(PLUGIN_BUNDLED_LIBRARIES)
  80 + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
  81 + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
  82 + COMPONENT Runtime)
  83 +endif()
  84 +
  85 +# Fully re-copy the assets directory on each build to avoid having stale files
  86 +# from a previous install.
  87 +set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
  88 +install(CODE "
  89 + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
  90 + " COMPONENT Runtime)
  91 +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
  92 + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
  93 +
  94 +# Install the AOT library on non-Debug builds only.
  95 +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
  96 + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
  97 + COMPONENT Runtime)
  98 +endif()
  1 +cmake_minimum_required(VERSION 3.10)
  2 +
  3 +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
  4 +
  5 +# Configuration provided via flutter tool.
  6 +include(${EPHEMERAL_DIR}/generated_config.cmake)
  7 +
  8 +# TODO: Move the rest of this into files in ephemeral. See
  9 +# https://github.com/flutter/flutter/issues/57146.
  10 +
  11 +# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
  12 +# which isn't available in 3.10.
  13 +function(list_prepend LIST_NAME PREFIX)
  14 + set(NEW_LIST "")
  15 + foreach(element ${${LIST_NAME}})
  16 + list(APPEND NEW_LIST "${PREFIX}${element}")
  17 + endforeach(element)
  18 + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
  19 +endfunction()
  20 +
  21 +# === Flutter Library ===
  22 +# System-level dependencies.
  23 +find_package(PkgConfig REQUIRED)
  24 +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
  25 +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
  26 +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
  27 +pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid)
  28 +
  29 +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
  30 +
  31 +# Published to parent scope for install step.
  32 +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
  33 +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
  34 +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
  35 +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
  36 +
  37 +list(APPEND FLUTTER_LIBRARY_HEADERS
  38 + "fl_basic_message_channel.h"
  39 + "fl_binary_codec.h"
  40 + "fl_binary_messenger.h"
  41 + "fl_dart_project.h"
  42 + "fl_engine.h"
  43 + "fl_json_message_codec.h"
  44 + "fl_json_method_codec.h"
  45 + "fl_message_codec.h"
  46 + "fl_method_call.h"
  47 + "fl_method_channel.h"
  48 + "fl_method_codec.h"
  49 + "fl_method_response.h"
  50 + "fl_plugin_registrar.h"
  51 + "fl_plugin_registry.h"
  52 + "fl_standard_message_codec.h"
  53 + "fl_standard_method_codec.h"
  54 + "fl_string_codec.h"
  55 + "fl_value.h"
  56 + "fl_view.h"
  57 + "flutter_linux.h"
  58 +)
  59 +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
  60 +add_library(flutter INTERFACE)
  61 +target_include_directories(flutter INTERFACE
  62 + "${EPHEMERAL_DIR}"
  63 +)
  64 +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
  65 +target_link_libraries(flutter INTERFACE
  66 + PkgConfig::GTK
  67 + PkgConfig::GLIB
  68 + PkgConfig::GIO
  69 + PkgConfig::BLKID
  70 +)
  71 +add_dependencies(flutter flutter_assemble)
  72 +
  73 +# === Flutter tool backend ===
  74 +# _phony_ is a non-existent file to force this command to run every time,
  75 +# since currently there's no way to get a full input/output list from the
  76 +# flutter tool.
  77 +add_custom_command(
  78 + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
  79 + ${CMAKE_CURRENT_BINARY_DIR}/_phony_
  80 + COMMAND ${CMAKE_COMMAND} -E env
  81 + ${FLUTTER_TOOL_ENVIRONMENT}
  82 + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
  83 + linux-x64 ${CMAKE_BUILD_TYPE}
  84 +)
  85 +add_custom_target(flutter_assemble DEPENDS
  86 + "${FLUTTER_LIBRARY}"
  87 + ${FLUTTER_LIBRARY_HEADERS}
  88 +)
  1 +//
  2 +// Generated file. Do not edit.
  3 +//
  4 +
  5 +#include "generated_plugin_registrant.h"
  6 +
  7 +
  8 +void fl_register_plugins(FlPluginRegistry* registry) {
  9 +}
  1 +//
  2 +// Generated file. Do not edit.
  3 +//
  4 +
  5 +#ifndef GENERATED_PLUGIN_REGISTRANT_
  6 +#define GENERATED_PLUGIN_REGISTRANT_
  7 +
  8 +#include <flutter_linux/flutter_linux.h>
  9 +
  10 +// Registers Flutter plugins.
  11 +void fl_register_plugins(FlPluginRegistry* registry);
  12 +
  13 +#endif // GENERATED_PLUGIN_REGISTRANT_
  1 +#
  2 +# Generated file, do not edit.
  3 +#
  4 +
  5 +list(APPEND FLUTTER_PLUGIN_LIST
  6 +)
  7 +
  8 +set(PLUGIN_BUNDLED_LIBRARIES)
  9 +
  10 +foreach(plugin ${FLUTTER_PLUGIN_LIST})
  11 + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
  12 + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
  13 + list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
  14 + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
  15 +endforeach(plugin)
  1 +#include "my_application.h"
  2 +
  3 +int main(int argc, char** argv) {
  4 + // Only X11 is currently supported.
  5 + // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932.
  6 + gdk_set_allowed_backends("x11");
  7 +
  8 + g_autoptr(MyApplication) app = my_application_new();
  9 + return g_application_run(G_APPLICATION(app), argc, argv);
  10 +}
  1 +#include "my_application.h"
  2 +
  3 +#include <flutter_linux/flutter_linux.h>
  4 +
  5 +#include "flutter/generated_plugin_registrant.h"
  6 +
  7 +struct _MyApplication {
  8 + GtkApplication parent_instance;
  9 +};
  10 +
  11 +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
  12 +
  13 +// Implements GApplication::activate.
  14 +static void my_application_activate(GApplication* application) {
  15 + GtkWindow* window =
  16 + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
  17 + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
  18 + gtk_widget_show(GTK_WIDGET(header_bar));
  19 + gtk_header_bar_set_title(header_bar, "example");
  20 + gtk_header_bar_set_show_close_button(header_bar, TRUE);
  21 + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
  22 + gtk_window_set_default_size(window, 1280, 720);
  23 + gtk_widget_show(GTK_WIDGET(window));
  24 +
  25 + g_autoptr(FlDartProject) project = fl_dart_project_new();
  26 +
  27 + FlView* view = fl_view_new(project);
  28 + gtk_widget_show(GTK_WIDGET(view));
  29 + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
  30 +
  31 + fl_register_plugins(FL_PLUGIN_REGISTRY(view));
  32 +
  33 + gtk_widget_grab_focus(GTK_WIDGET(view));
  34 +}
  35 +
  36 +static void my_application_class_init(MyApplicationClass* klass) {
  37 + G_APPLICATION_CLASS(klass)->activate = my_application_activate;
  38 +}
  39 +
  40 +static void my_application_init(MyApplication* self) {}
  41 +
  42 +MyApplication* my_application_new() {
  43 + return MY_APPLICATION(g_object_new(my_application_get_type(),
  44 + "application-id", APPLICATION_ID,
  45 + nullptr));
  46 +}
  1 +#ifndef FLUTTER_MY_APPLICATION_H_
  2 +#define FLUTTER_MY_APPLICATION_H_
  3 +
  4 +#include <gtk/gtk.h>
  5 +
  6 +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
  7 + GtkApplication)
  8 +
  9 +/**
  10 + * my_application_new:
  11 + *
  12 + * Creates a new Flutter-based application.
  13 + *
  14 + * Returns: a new #MyApplication.
  15 + */
  16 +MyApplication* my_application_new();
  17 +
  18 +#endif // FLUTTER_MY_APPLICATION_H_
@@ -88,7 +88,7 @@ class GetObserver extends NavigatorObserver { @@ -88,7 +88,7 @@ class GetObserver extends NavigatorObserver {
88 value.route = route; 88 value.route = route;
89 value.isBack = false; 89 value.isBack = false;
90 value.removed = ''; 90 value.removed = '';
91 - value.previous = '${previousRoute?.settings?.name}'; 91 + value.previous = previousRoute?.settings?.name ?? '';
92 value.isSnackbar = isSnackbar; 92 value.isSnackbar = isSnackbar;
93 value.isBottomSheet = isBottomSheet; 93 value.isBottomSheet = isBottomSheet;
94 value.isDialog = isDialog; 94 value.isDialog = isDialog;
@@ -119,12 +119,12 @@ class GetObserver extends NavigatorObserver { @@ -119,12 +119,12 @@ class GetObserver extends NavigatorObserver {
119 119
120 _routeSend.update((value) { 120 _routeSend.update((value) {
121 if (previousRoute is PageRoute) 121 if (previousRoute is PageRoute)
122 - value.current = '${previousRoute?.settings?.name}'; 122 + value.current = previousRoute?.settings?.name ?? '';
123 value.args = route?.settings?.arguments; 123 value.args = route?.settings?.arguments;
124 value.route = previousRoute; 124 value.route = previousRoute;
125 value.isBack = true; 125 value.isBack = true;
126 value.removed = ''; 126 value.removed = '';
127 - value.previous = '${route?.settings?.name}'; 127 + value.previous = route?.settings?.name ?? '';
128 value.isSnackbar = false; 128 value.isSnackbar = false;
129 value.isBottomSheet = false; 129 value.isBottomSheet = false;
130 value.isDialog = false; 130 value.isDialog = false;
@@ -142,7 +142,7 @@ class GetObserver extends NavigatorObserver { @@ -142,7 +142,7 @@ class GetObserver extends NavigatorObserver {
142 GetConfig.currentRoute = name(newRoute); 142 GetConfig.currentRoute = name(newRoute);
143 143
144 _routeSend.update((value) { 144 _routeSend.update((value) {
145 - if (newRoute is PageRoute) value.current = '${newRoute?.settings?.name}'; 145 + if (newRoute is PageRoute) value.current = newRoute?.settings?.name ?? '';
146 value.args = newRoute?.settings?.arguments; 146 value.args = newRoute?.settings?.arguments;
147 value.route = newRoute; 147 value.route = newRoute;
148 value.isBack = false; 148 value.isBack = false;
@@ -163,8 +163,8 @@ class GetObserver extends NavigatorObserver { @@ -163,8 +163,8 @@ class GetObserver extends NavigatorObserver {
163 _routeSend.update((value) { 163 _routeSend.update((value) {
164 value.route = previousRoute; 164 value.route = previousRoute;
165 value.isBack = false; 165 value.isBack = false;
166 - value.removed = '${route?.settings?.name}';  
167 - value.previous = '${route?.settings?.name}'; 166 + value.removed = route?.settings?.name ?? '';
  167 + value.previous = route?.settings?.name ?? '';
168 }); 168 });
169 if (routing != null) routing(_routeSend); 169 if (routing != null) routing(_routeSend);
170 } 170 }
@@ -3,6 +3,8 @@ import 'package:flutter/widgets.dart'; @@ -3,6 +3,8 @@ import 'package:flutter/widgets.dart';
3 import 'package:get/src/state_manager/rx/rx_core/rx_interface.dart'; 3 import 'package:get/src/state_manager/rx/rx_core/rx_interface.dart';
4 import '../rx_core/rx_impl.dart'; 4 import '../rx_core/rx_impl.dart';
5 5
  6 +typedef WidgetCallback = Widget Function();
  7 +
6 /// The simplest reactive widget in GetX. 8 /// The simplest reactive widget in GetX.
7 /// 9 ///
8 /// Just pass your Rx variable in the root scope of the callback to have it 10 /// Just pass your Rx variable in the root scope of the callback to have it
@@ -11,7 +13,7 @@ import '../rx_core/rx_impl.dart'; @@ -11,7 +13,7 @@ import '../rx_core/rx_impl.dart';
11 /// final _name = "GetX".obs; 13 /// final _name = "GetX".obs;
12 /// Obx(() => Text( _name.value )),... ; 14 /// Obx(() => Text( _name.value )),... ;
13 class Obx extends StatefulWidget { 15 class Obx extends StatefulWidget {
14 - final Widget Function() builder; 16 + final WidgetCallback builder;
15 17
16 const Obx(this.builder); 18 const Obx(this.builder);
17 _ObxState createState() => _ObxState(); 19 _ObxState createState() => _ObxState();