README docs additions!
- fixes #470 - added main README section docs called "Useful tips" where i mentioned some stuffs..
Showing
1 changed file
with
259 additions
and
33 deletions
@@ -355,23 +355,25 @@ return GetMaterialApp( | @@ -355,23 +355,25 @@ return GetMaterialApp( | ||
355 | 355 | ||
356 | ## Change Theme | 356 | ## Change Theme |
357 | 357 | ||
358 | -Please do not use any higher level widget than GetMaterialApp in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with Get. | 358 | +Please do not use any higher level widget than `GetMaterialApp` in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with **GetX™**. |
359 | 359 | ||
360 | -You can create your custom theme and simply add it within Get.changeTheme without any boilerplate for that: | 360 | +You can create your custom theme and simply add it within `Get.changeTheme` without any boilerplate for that: |
361 | 361 | ||
362 | ```dart | 362 | ```dart |
363 | Get.changeTheme(ThemeData.light()); | 363 | Get.changeTheme(ThemeData.light()); |
364 | ``` | 364 | ``` |
365 | 365 | ||
366 | -If you want to create something like a button that changes the theme with onTap, you can combine two Get APIs for that, the api that checks if the dark theme is being used, and the theme change API, you can just put this within an onPressed: | 366 | +If you want to create something like a button that changes the Theme in `onTap`, you can combine two **GetX™** APIs for that: |
367 | +- The api that checks if the dark `Theme` is being used. | ||
368 | +- And the `Theme` Change API, you can just put this within an `onPressed`: | ||
367 | 369 | ||
368 | ```dart | 370 | ```dart |
369 | Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); | 371 | Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); |
370 | ``` | 372 | ``` |
371 | 373 | ||
372 | -When darkmode is activated, it will switch to the light theme, and when the light theme is activated, it will change to dark. | 374 | +When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. |
373 | 375 | ||
374 | -If you want to know in depth how to change the theme, you can follow this tutorial on Medium that even teaches the persistence of the theme using Get: | 376 | +If you want to know in depth how to change the Theme, you can follow this tutorial on Medium which even teaches the persistence of the theme using **GetX™**: |
375 | 377 | ||
376 | - [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). | 378 | - [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). |
377 | 379 | ||
@@ -417,60 +419,73 @@ Get.offNamedUntil() | @@ -417,60 +419,73 @@ Get.offNamedUntil() | ||
417 | //Check in what platform the app is running | 419 | //Check in what platform the app is running |
418 | GetPlatform.isAndroid | 420 | GetPlatform.isAndroid |
419 | GetPlatform.isIOS | 421 | GetPlatform.isIOS |
422 | +GetPlatform.isMacOS | ||
423 | +GetPlatform.isWindows | ||
424 | +GetPlatform.isLinux | ||
425 | +GetPlatform.isFuchsia | ||
426 | + | ||
427 | +//Check the device type | ||
428 | +GetPlatform.isMobile | ||
429 | +GetPlatform.isDesktop | ||
430 | +//All platforms are supported independently in web! | ||
431 | +//You can tell if you are running inside a browser | ||
432 | +//on Windows, iOS, OSX, Android, etc. | ||
420 | GetPlatform.isWeb | 433 | GetPlatform.isWeb |
421 | 434 | ||
422 | -// Equivalent to the method: MediaQuery.of(context).size.height, but they are immutable. | 435 | + |
436 | +// Equivalent to : MediaQuery.of(context).size.height, | ||
437 | +// but immutable. | ||
423 | Get.height | 438 | Get.height |
424 | Get.width | 439 | Get.width |
425 | 440 | ||
426 | -// Gives the current context of navigator. | 441 | +// Gives the current context of the Navigator. |
427 | Get.context | 442 | Get.context |
428 | 443 | ||
429 | -// Gives the context of the snackbar/dialog/bottomsheet in the foreground anywhere in your code. | 444 | +// Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. |
430 | Get.contextOverlay | 445 | Get.contextOverlay |
431 | 446 | ||
432 | // Note: the following methods are extensions on context. Since you | 447 | // Note: the following methods are extensions on context. Since you |
433 | // have access to context in any place of your UI, you can use it anywhere in the UI code | 448 | // have access to context in any place of your UI, you can use it anywhere in the UI code |
434 | 449 | ||
435 | -// If you need a changeable height/width (like browser windows that can be scaled) you will need to use context. | 450 | +// If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. |
436 | context.width | 451 | context.width |
437 | context.height | 452 | context.height |
438 | 453 | ||
439 | -// gives you the power to define half the screen now, a third of it and so on. | ||
440 | -//Useful for responsive applications. | 454 | +// Gives you the power to define half the screen, a third of it and so on. |
455 | +// Useful for responsive applications. | ||
441 | // param dividedBy (double) optional - default: 1 | 456 | // param dividedBy (double) optional - default: 1 |
442 | // param reducedBy (double) optional - default: 0 | 457 | // param reducedBy (double) optional - default: 0 |
443 | context.heightTransformer() | 458 | context.heightTransformer() |
444 | context.widthTransformer() | 459 | context.widthTransformer() |
445 | 460 | ||
446 | -/// similar to MediaQuery.of(context).size | 461 | +/// Similar to MediaQuery.of(context).size |
447 | context.mediaQuerySize() | 462 | context.mediaQuerySize() |
448 | 463 | ||
449 | -/// similar to MediaQuery.of(context).padding | 464 | +/// Similar to MediaQuery.of(context).padding |
450 | context.mediaQueryPadding() | 465 | context.mediaQueryPadding() |
451 | 466 | ||
452 | -/// similar to MediaQuery.of(context).viewPadding | 467 | +/// Similar to MediaQuery.of(context).viewPadding |
453 | context.mediaQueryViewPadding() | 468 | context.mediaQueryViewPadding() |
454 | 469 | ||
455 | -/// similar to MediaQuery.of(context).viewInsets; | 470 | +/// Similar to MediaQuery.of(context).viewInsets; |
456 | context.mediaQueryViewInsets() | 471 | context.mediaQueryViewInsets() |
457 | 472 | ||
458 | -/// similar to MediaQuery.of(context).orientation; | 473 | +/// Similar to MediaQuery.of(context).orientation; |
459 | context.orientation() | 474 | context.orientation() |
460 | 475 | ||
461 | -/// check if device is on landscape mode | 476 | +/// Check if device is on landscape mode |
462 | context.isLandscape() | 477 | context.isLandscape() |
463 | 478 | ||
464 | -/// check if device is on portrait mode | 479 | +/// Check if device is on portrait mode |
465 | context.isPortrait() | 480 | context.isPortrait() |
466 | 481 | ||
467 | -/// similar to MediaQuery.of(context).devicePixelRatio; | 482 | +/// Similar to MediaQuery.of(context).devicePixelRatio; |
468 | context.devicePixelRatio() | 483 | context.devicePixelRatio() |
469 | 484 | ||
470 | -/// similar to MediaQuery.of(context).textScaleFactor; | 485 | +/// Similar to MediaQuery.of(context).textScaleFactor; |
471 | context.textScaleFactor() | 486 | context.textScaleFactor() |
472 | 487 | ||
473 | -/// get the shortestSide from screen | 488 | +/// Get the shortestSide from screen |
474 | context.mediaQueryShortestSide() | 489 | context.mediaQueryShortestSide() |
475 | 490 | ||
476 | /// True if width be larger than 800 | 491 | /// True if width be larger than 800 |
@@ -488,9 +503,9 @@ context.isLargeTablet() | @@ -488,9 +503,9 @@ context.isLargeTablet() | ||
488 | /// True if the current device is Tablet | 503 | /// True if the current device is Tablet |
489 | context.isTablet() | 504 | context.isTablet() |
490 | 505 | ||
491 | -/// Returns a value according to the screen size | ||
492 | -/// can give value for | ||
493 | -/// swatch: if the shortestSide is smaller than 300 | 506 | +/// Returns a value<T> according to the screen size |
507 | +/// can give value for: | ||
508 | +/// watch: if the shortestSide is smaller than 300 | ||
494 | /// mobile: if the shortestSide is smaller than 600 | 509 | /// mobile: if the shortestSide is smaller than 600 |
495 | /// tablet: if the shortestSide is smaller than 1200 | 510 | /// tablet: if the shortestSide is smaller than 1200 |
496 | /// desktop: if width is largest than 1200 | 511 | /// desktop: if width is largest than 1200 |
@@ -508,7 +523,7 @@ MaterialApp( | @@ -508,7 +523,7 @@ MaterialApp( | ||
508 | ); | 523 | ); |
509 | ``` | 524 | ``` |
510 | 525 | ||
511 | -You will also be able to use your own Middleware within GetObserver, this will not influence anything. | 526 | +You will also be able to use your own Middleware within `GetObserver`, this will not influence anything. |
512 | 527 | ||
513 | ```dart | 528 | ```dart |
514 | MaterialApp( | 529 | MaterialApp( |
@@ -519,7 +534,8 @@ MaterialApp( | @@ -519,7 +534,8 @@ MaterialApp( | ||
519 | ); | 534 | ); |
520 | ``` | 535 | ``` |
521 | 536 | ||
522 | -You can create Global settings for Get. Just add Get.config to your code before pushing any route or do it directly in your GetMaterialApp | 537 | +You can create _Global Settings_ for `Get`. Just add `Get.config` to your code before pushing any route. |
538 | +Or do it directly in your `GetMaterialApp` | ||
523 | 539 | ||
524 | ```dart | 540 | ```dart |
525 | GetMaterialApp( | 541 | GetMaterialApp( |
@@ -538,7 +554,9 @@ Get.config( | @@ -538,7 +554,9 @@ Get.config( | ||
538 | ) | 554 | ) |
539 | ``` | 555 | ``` |
540 | 556 | ||
541 | -You can optionally redirect all the logging messages from Get. If you want to use your own favourite logging package and want to capture the logs there. | 557 | +You can optionally redirect all the logging messages from `Get`. |
558 | +If you want to use your own, favourite logging package, | ||
559 | +and want to capture the logs there: | ||
542 | 560 | ||
543 | ```dart | 561 | ```dart |
544 | GetMaterialApp( | 562 | GetMaterialApp( |
@@ -558,12 +576,12 @@ void localLogWriter(String text, {bool isError = false}) { | @@ -558,12 +576,12 @@ void localLogWriter(String text, {bool isError = false}) { | ||
558 | 576 | ||
559 | These Widgets allows you to manage a single value, and keep the state ephemeral and locally. | 577 | These Widgets allows you to manage a single value, and keep the state ephemeral and locally. |
560 | We have flavours for Reactive and Simple. | 578 | We have flavours for Reactive and Simple. |
561 | -For instance, you might use them to toggle obscureText in a TextField, maybe create a custom | ||
562 | -Expandable Panel, or maybe modify the current index in BottomNavigationBar while changing the content | ||
563 | -of the body in a Scaffold. | 579 | +For instance, you might use them to toggle obscureText in a `TextField`, maybe create a custom |
580 | +Expandable Panel, or maybe modify the current index in `BottomNavigationBar` while changing the content | ||
581 | +of the body in a `Scaffold`. | ||
564 | 582 | ||
565 | #### ValueBuilder | 583 | #### ValueBuilder |
566 | -A simplification of StatefulWidget that works with a "setState" callback that takes the updated value. | 584 | +A simplification of `StatefulWidget` that works with a `.setState` callback that takes the updated value. |
567 | 585 | ||
568 | ```dart | 586 | ```dart |
569 | ValueBuilder<bool>( | 587 | ValueBuilder<bool>( |
@@ -578,8 +596,8 @@ ValueBuilder<bool>( | @@ -578,8 +596,8 @@ ValueBuilder<bool>( | ||
578 | ), | 596 | ), |
579 | ``` | 597 | ``` |
580 | 598 | ||
581 | -#### ObxValue | ||
582 | -Similar to ValueBuilder, but this is the Reactive version, you pass a Rx instance (remember the magical .obs?) and | 599 | +####ObxValue |
600 | +Similar to [`ValueBuilder`](#valuebuilder), but this is the Reactive version, you pass a Rx instance (remember the magical .obs?) and | ||
583 | updates automatically... isn't it awesome? | 601 | updates automatically... isn't it awesome? |
584 | 602 | ||
585 | ```dart | 603 | ```dart |
@@ -591,6 +609,214 @@ ObxValue((data) => Switch( | @@ -591,6 +609,214 @@ ObxValue((data) => Switch( | ||
591 | ), | 609 | ), |
592 | ``` | 610 | ``` |
593 | 611 | ||
612 | +## Useful tips | ||
613 | + | ||
614 | + | ||
615 | +`.obs`ervables (also known as _Rx_ Types) have a wide variety of internal methods and operators. | ||
616 | + | ||
617 | +> Is very common to _believe_ that a property with `.obs` **IS** the actual value... but make no mistake! | ||
618 | +We avoid the Type declaration of the variable, because Dart's compiler is smart enough, and the code | ||
619 | +looks cleaner, but: | ||
620 | +```dart | ||
621 | +var message = 'Hello world'.obs; | ||
622 | +print( 'Message "$message" has Type ${message.runtimeType}'); | ||
623 | +``` | ||
624 | +Even if `message` _prints_ the actual String value, the Type is **RxString**! | ||
625 | + | ||
626 | +So, you can't do `message.substring( 0, 4 )`. | ||
627 | +You have to access the real `value` inside the _observable_: | ||
628 | +The most "used way" is `.value`, but, did you know that you can also use... | ||
629 | + | ||
630 | +```dart | ||
631 | +final name = 'GetX'.obs; | ||
632 | +// only "updates" the stream, if the value is different from the current one. | ||
633 | +name.value = 'Hey'; | ||
634 | + | ||
635 | +// this weird (and kinda cool) assignment, updates the stream no matter what | ||
636 | +// it takes nulls, or same value... but rebuilds the observers. | ||
637 | +name << 'Hey'; // ! | ||
638 | + | ||
639 | +// All Rx properties are "callable" and returns the new value. | ||
640 | +// but this approach does not accepts `null`, the UI will not rebuild. | ||
641 | +name('Hello'); | ||
642 | + | ||
643 | +// is like a getter, prints 'Hello'. | ||
644 | +name() ; | ||
645 | + | ||
646 | +/// numbers: | ||
647 | + | ||
648 | +final count = 0.obs; | ||
649 | + | ||
650 | +// you can just most basic operators acts on the property! | ||
651 | +count + 1; | ||
652 | + | ||
653 | +// Watch out! this is only valid if `count` is not final, but var | ||
654 | +count += 1; | ||
655 | + | ||
656 | +// You can also compare against values: | ||
657 | +count > 2; | ||
658 | + | ||
659 | +/// booleans: | ||
660 | + | ||
661 | +final flag = false.obs; | ||
662 | + | ||
663 | +// switches the value between true/false | ||
664 | +flag.toggle(); | ||
665 | + | ||
666 | + | ||
667 | +/// all types: | ||
668 | + | ||
669 | +// Sets the `value` to null. | ||
670 | +flag.nil(); | ||
671 | + | ||
672 | +// All toString(), toJson() operations are passed down to the `value` | ||
673 | +print( count ); // calls `toString()` inside for RxInt | ||
674 | + | ||
675 | +final abc = [0,1,2].obs; | ||
676 | +// Converts the value to a json Array, prints RxList | ||
677 | +// Json is supported by all Rx types! | ||
678 | +print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); | ||
679 | + | ||
680 | +// RxMap, RxList and RxSet are special Rx types, that extends their native types. | ||
681 | +// but you can work with a List as a regular list, although is reactive! | ||
682 | +abc.add(12); // pushes 12 to the list, and UPDATES the stream. | ||
683 | +abc[3]; // like Lists, reads the index 3. | ||
684 | + | ||
685 | + | ||
686 | +// equality works with the Rx and the value, but hashCode is always taken from the value | ||
687 | +final number = 12.obs; | ||
688 | +print( number == 12 ); // prints > true | ||
689 | + | ||
690 | +/// Custom Rx Models: | ||
691 | + | ||
692 | +// toJson(), toString() are deffered to the child, so you can implement override them, and print() the observable directly. | ||
693 | + | ||
694 | +class User { | ||
695 | + String name, last; | ||
696 | + int age; | ||
697 | + User({this.name, this.last, this.age}); | ||
698 | + | ||
699 | + @override | ||
700 | + String toString() => '$name $last, $age years old'; | ||
701 | +} | ||
702 | + | ||
703 | +final user = User(name: 'John', last: 'Doe', age: 33).obs; | ||
704 | + | ||
705 | +// `user` is "reactive", but the properties inside ARE NOT! | ||
706 | +// So, if we change some variable inside of it... | ||
707 | +user.value.name = 'Roi'; | ||
708 | +// The widget will not rebuild!, | ||
709 | +// `Rx` don't have any clue when you change something inside user. | ||
710 | +// So, for custom classes, we need to manually "notify" the change. | ||
711 | +user.refresh(); | ||
712 | + | ||
713 | +// or we can use the `update()` method! | ||
714 | +user.update((value){ | ||
715 | + value.name='Roi'; | ||
716 | +}); | ||
717 | + | ||
718 | +print( user ); | ||
719 | + | ||
720 | +// this also works. | ||
721 | +user << user.value; | ||
722 | + | ||
723 | +``` | ||
724 | + | ||
725 | +#### GetView | ||
726 | + | ||
727 | +I love this Widget, is so simple, yet, so useful! | ||
728 | + | ||
729 | +Is a `const Stateless` Widget that has a getter `controller` for a registered `Controller`, that's all. | ||
730 | + | ||
731 | +```dart | ||
732 | + class AwesomeController extends GetxController { | ||
733 | + final String title = 'My Awesome View'; | ||
734 | + } | ||
735 | + | ||
736 | + // ALWAYS remember to pass the `Type` you used to register your controller! | ||
737 | + class AwesomeView extends GetView<AwesomeController> { | ||
738 | + @override | ||
739 | + Widget build(BuildContext context) { | ||
740 | + return Container( | ||
741 | + padding: EdgeInsets.all(20), | ||
742 | + child: Text( controller.title ), // just call `controller.something` | ||
743 | + ); | ||
744 | + } | ||
745 | + } | ||
746 | +``` | ||
747 | + | ||
748 | +#### GetWidget | ||
749 | + | ||
750 | +Most people have no idea about this Widget, or totally confuse the usage of it. | ||
751 | +The use case is very rare, but very specific: It `caches` a Controller. | ||
752 | +Because of the _cache_, can't be a `const Stateless`. | ||
753 | + | ||
754 | +> So, when do you need to "cache" a Controller? | ||
755 | + | ||
756 | +If you use, another "not so common" feature of **GetX**: `Get.create()`. | ||
757 | + | ||
758 | +`Get.create(()=>Controller())` will generate a new `Controller` each time you call | ||
759 | +`Get.find<Controller>()`, | ||
760 | + | ||
761 | +That's where `GetWidget` shines... as you can use it, for example, | ||
762 | +to keep a list of Todo items. So, if the widget gets "rebuilt", it will keep the same controller instance. | ||
763 | + | ||
764 | + | ||
765 | +#### GetxService | ||
766 | + | ||
767 | +This class is like a `GetxController`, it shares the same lifecycle ( `onInit()`, `onReady()`, `onClose()`). | ||
768 | +But has no "logic" inside of it. It just notifies **GetX** Dependency Injection system, that this subclass | ||
769 | +**can not** be removed from memory. | ||
770 | + | ||
771 | +So is super useful to keep your "Services" always reachable and active with `Get.find()`. Like: | ||
772 | +`ApiService`, `StorageService`, `CacheService`. | ||
773 | + | ||
774 | +```dart | ||
775 | +Future<void> main() async { | ||
776 | + await initServices(); /// AWAIT SERVICES INITIALIZATION. | ||
777 | + runApp(SomeApp()); | ||
778 | +} | ||
779 | + | ||
780 | +/// Is a smart move to make your Services intiialize before you run the Flutter app. | ||
781 | +/// as you can control the execution flow (maybe you need to load some Theme configuration, | ||
782 | +/// apiKey, language defined by the User... so load SettingService before running ApiService. | ||
783 | +/// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. | ||
784 | +void initServices() async { | ||
785 | + print('starting services ...'); | ||
786 | + /// Here is where you put get_storage, hive, shared_pref initialization. | ||
787 | + /// or moor connection, or whatever that's async. | ||
788 | + await Get.putAsync(() => DbService().init()); | ||
789 | + await Get.put(SettingsService()).init(); | ||
790 | + print('All services started...'); | ||
791 | +} | ||
792 | + | ||
793 | +class DbService extends GetxService { | ||
794 | + Future<DbService> init() async { | ||
795 | + print('$runtimeType delays 2 sec'); | ||
796 | + await 2.delay(); | ||
797 | + print('$runtimeType ready!'); | ||
798 | + return this; | ||
799 | + } | ||
800 | +} | ||
801 | + | ||
802 | +class SettingsService extends GetxService { | ||
803 | + void init() async { | ||
804 | + print('$runtimeType delays 2 sec'); | ||
805 | + await 1.delay(); | ||
806 | + print('$runtimeType ready!'); | ||
807 | + } | ||
808 | +} | ||
809 | + | ||
810 | +``` | ||
811 | + | ||
812 | +The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a | ||
813 | +"Hot Reboot" of your app. So remember, if you need absolute persistance of a class instance during the | ||
814 | +lifetime of your app, use `GetxService`. | ||
815 | + | ||
816 | + | ||
817 | + | ||
818 | + | ||
819 | + | ||
594 | ## Video explanation of Other GetX Features | 820 | ## Video explanation of Other GetX Features |
595 | 821 | ||
596 | 822 |
-
Please register or login to post a comment