Skip to content

Instantly share code, notes, and snippets.

@yasinarik
Created December 23, 2020 14:59
Show Gist options
  • Save yasinarik/2dfdf932c784897d51215bef543ecd55 to your computer and use it in GitHub Desktop.
Save yasinarik/2dfdf932c784897d51215bef543ecd55 to your computer and use it in GitHub Desktop.
// ATTENTION!
//You have to add these to pubspec.yaml:
//get: ^3.24.0
//provider: ^4.3.2+3
//uuid: ^2.2.2
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
/* -------------------------------------------------------------------------- */
/* main */
/* -------------------------------------------------------------------------- */
void main() async {
runApp(App());
}
/* -------------------------------------------------------------------------- */
/* App */
/* -------------------------------------------------------------------------- */
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: MainView(),
);
}
}
/* -------------------------------------------------------------------------- */
/* TagPVD */
/* -------------------------------------------------------------------------- */
class TagPVD with ChangeNotifier {
TagPVD() {
init();
}
/// Generates a random uuid on init.
init() => tag = Uuid().v1();
String tag = "";
}
/* -------------------------------------------------------------------------- */
/* InstanceSeparator */
/* -------------------------------------------------------------------------- */
/// InstanceSeparator basically creates a new pair of ChangeNotifierProvider-Consumer
/// every time a new instance of MainView is created in the widget tree.
/// This new pair uses the TagPVD (TagProvider, shortened).
///
/// TagPVD has an init method inside of its constructor. It basically generates a new Uuid String and set as the tag variable.
/// So, every children of the InstanceSeparator widget have access to the TagPVD respectively.
class InstanceSeparator extends StatelessWidget {
final Function(String uniqueTag) builder;
InstanceSeparator({
Key key,
@required this.builder,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TagPVD>(
create: (context) => TagPVD(),
child: Consumer<TagPVD>(
builder: (context, tagPVD, _) {
return builder(tagPVD.tag); // This is the builder property of InstanceSeparator. Not related to provider. We will use this
},
),
);
}
}
/* -------------------------------------------------------------------------- */
/* MainView */
/* -------------------------------------------------------------------------- */
class MainView extends StatelessWidget {
/// The "controller" variable is defined here. But haven't set it to anything yet. DON'T make it final.
/// Why here? Because I want to access "controller" variable inside custom methods of this MainView class.
/// I generally need some class methods. They improve readability too.
/// For ex: buildList(), buildHeader(), buildProfilePicture()
/// If I didn't define the "controller" variable here, then I would have to pass it to the methods(functions).
/// For ex: buildList(controller), buildHeader(controller), buildProfilePicture(controller)
Controller controller;
@override
Widget build(BuildContext context) {
/// Instance separator wraps all of the children of MainView.
return InstanceSeparator(
builder: (uniqueTag) {
/// the "controller" variables are set inside here. We can grab the "uniqueTag".
/// This "uniqueTag" parameter is shared down to the widget tree.
/// Child widgets can look for this parameter up in the tree because the provider indeed an InheritedWidget.
controller = Get.put(Controller(), tag: uniqueTag);
return Obx(() {
return Scaffold(
body: Center(
child: Column(
children: [
// Title
Container(
padding: EdgeInsets.all(16),
margin: EdgeInsets.all(16),
color: Colors.red,
child: Text("Example usage of GetX / Obx with a \"tag\" property."),
),
/// Button
/// Navigating to a new screen, in our case a new MainView.
/// So the new MainView will have a separate instance of Get controller.
/// Also, all of its children will share the same controller with the help of InstanceSeparator.
GestureDetector(
onTap: () {
Get.to(MainView(), preventDuplicates: false);
},
child: Container(
color: Colors.pink,
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Text("TAP --> Navigate to a new MainView"),
),
),
// Button
GestureDetector(
onTap: () {
controller.addItemToList();
},
child: Container(
color: Colors.blue,
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Text("TAP --> Add item to list"),
),
),
// ListView with builder
Container(
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: controller.itemList.length,
itemBuilder: (_, i) {
return ListItemWidget(index: i);
},
),
),
// ListView with a class method. Just for demonstration.
Container(
height: 200,
child: ListView(
scrollDirection: Axis.horizontal,
children: buildListView(),
),
),
],
),
),
);
});
},
);
}
/// Obviously, this method is not needed. It is here just because of demonstration purposes.
/// Here we use the "controller" without calling it as a parameter inside --> buildList() method.
/// Because at the beginning of the MainView class, we already defined it as --> Controller controller;
/// It might be confusing but if you use custom methods inside a widget class like me,
/// it is better to define them first without setting to anything.
List<Widget> buildListView() {
List<Widget> wl = [];
Widget w;
/// Reminder: We set the "controller" variable inside --> InstanceSeparator --> controller = Get.put(Controller(), tag: uniqueTag);
for (var i = 0; i < controller.itemList.length; i++) {
w = ListItemWidget(index: i);
wl.add(w);
}
return wl;
}
}
/* -------------------------------------------------------------------------- */
/* ListItemWidget */
/* -------------------------------------------------------------------------- */
class ListItemWidget extends StatelessWidget {
final int index;
ListItemWidget({
this.index,
});
/// Again, in order to make "controller" and "tagPVD" variables available inside custom class methods,
/// we define them here. (not final). We will set them later.
Controller controller;
TagPVD tagPVD;
@override
Widget build(BuildContext context) {
/// As a restriction of Provider package, we have to use "context".
/// So I have to set "tagPVD" just here inside main build() method.
/// On the other hand, Provider let us access to the "RIGHT" instance just by looking for the tree above.
tagPVD = Provider.of<TagPVD>(context);
/// Since we now have "tagPVD", we can get the "tag" property of it. Which is the RIGHT String for us to use.
controller = Get.find(tag: tagPVD.tag);
/// The rest is the same as regular usage of Obx(()=>Widget());
return Obx(() {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
controller.increaseCounterAtIndex(index);
},
child: Container(
margin: EdgeInsets.all(16),
height: 200,
color: Colors.yellow,
child: Column(
children: [
Container(
padding: EdgeInsets.all(12),
child: Text("This is a ListItemWidget with index: $index"),
),
buildCounterSection(),
Container(
padding: EdgeInsets.all(12),
child: Text("TAP to increase this counter"),
),
],
),
),
);
});
}
/// Let's use a method. We don't have to pass the "controller". --> Widget buildCounterSection(Controller controller) {}
Widget buildCounterSection() {
return Container(
padding: EdgeInsets.all(12),
child: Text("Counter of this item: " + controller.itemList[index].value.toString()),
);
}
}
/* -------------------------------------------------------------------------- */
/* Controller */
/* -------------------------------------------------------------------------- */
class Controller extends GetxController {
var itemList = <RxInt>[].obs;
addItemToList() {
itemList.add(0.obs);
}
increaseCounterAtIndex(int index) {
itemList[index].value++;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment