Comments (10)
Hey @arthurgustin -- thanks for writing in! Yah, text fields are a bit of a tricky one overall! No worries about the confusion :)
I'll try to find time to check out your code tonight after I've wrapped up work.
from flutter_redux.
@brianegan Thanks man :)
from flutter_redux.
Hey @brianegan , any new on this topic ? If you don't have time for this, maybe you can give me a pointer to a viable alternative (if it exists) ?
from flutter_redux.
Hey @arthurgustin, just got a chance to look through the code! So, it looks like you've got things hooked up in slightly the wrong way. Looking at your code, #20 should not be the problem.
One fundamental concept to understand: In order to share state between Screens, you need to lift that state above all of your screens! In Flutter, this means wrapping MaterialApp in an InheritedWidget (such as the StoreProvider) or a StatefulWidget.
Right now, you're only wrapping your first screen with a StoreProvider. This means that any children of the first screen will have access to the Store, but siblings (such as different pages) will NOT have access to the store. This is why you might get a null error when trying to access the store on the second page!
Also, no need to creat your own Store singleton. That's really the job of the StoreProvider: hand it a Store and it will hold it as a singleton and pass it around!
Sorry, didn't have time to clean this up, but here's a working example:
import 'dart:async';
import 'package:fluro/fluro.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
import 'package:redux/redux.dart';
class SetFirstNameAction {
final String firsName;
SetFirstNameAction(this.firsName);
}
String firstNameReducer(String state, dynamic action) {
if (action is SetFirstNameAction) {
return action.firsName;
}
return state;
}
class Child {
String firstName;
}
class Routes {
static final Router router = configureRoutes(new Router());
static Router configureRoutes(Router router) {
// Define our home page.
router.define('/', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return new HomePage();
}));
// Define our about page.
router.define('/kids/register/1', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return new MyInheritedWidget(
data: MediaQuery.of(context),
child: new RegisterKidPage1(),
);
}));
// Define our about page.
router.define('/kids/register/2', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return new MyInheritedWidget(
data: MediaQuery.of(context),
child: new RegisterKidPage2(),
);
}));
return router;
}
}
void main() {
runApp(new StoreProvider(
store: new Store<String>(firstNameReducer, initialState: ""),
child: new MaterialApp(
onGenerateRoute: Routes.router.generator,
),
));
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Teddycare HomePage"),
backgroundColor: Colors.deepOrange),
body: new Container(
child: new Center(
child: new Column(
children: [
new RaisedButton(
child: new Text('Registration page'),
onPressed: () {
Navigator.of(context).pushNamed("/kids/register/1");
},
),
],
))));
}
}
class MyInheritedWidget extends InheritedWidget {
static MyInheritedWidget of(BuildContext context) =>
context.inheritFromWidgetOfExactType(MyInheritedWidget);
final MediaQueryData _data;
String get deviceWidth => _data.size.width.toString();
Orientation get orientation => _data.orientation;
MyInheritedWidget({
Key key,
MediaQueryData data,
Widget child,
})
: _data = data,
super(key: key, child: child);
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) =>
_data != oldWidget._data;
}
class RegisterKidPage1 extends StatefulWidget {
RegisterKidPage1({Key key}) : super(key: key);
@override
_RegisterKidPage1 createState() => new _RegisterKidPage1();
}
class _RegisterKidPage1 extends State<RegisterKidPage1> {
var numberOfHorizontalCell = 2;
var ratio = 4.5;
TextEditingController childFirstNameController = new TextEditingController();
FocusNode childFirstNameFocus = new FocusNode();
Widget _buildForm(BuildContext context) {
return new Form(
child: new Container(
child: new Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
new Flexible(
child: new GridView.count(
crossAxisCount: numberOfHorizontalCell,
childAspectRatio: ratio,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: [
new StoreConnector<String, String>(
converter: (store) => store.state,
builder: (context, count) => new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Firstname",
),
controller: childFirstNameController,
focusNode: childFirstNameFocus,
initialValue: count,
//onChanged: _setFirstName,
// initialValue: child.firstName,
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Lastname",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Relationship",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Phone",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Address Line 1",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Address Line 2",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "City",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "State",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Zip",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Email Address",
),
),
),
]),
),
new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
new Padding(
padding: const EdgeInsets.all(16.0),
child: new RaisedButton(
onPressed: () {
new StoreProvider.of(context).store.dispatch(
new SetFirstNameAction(childFirstNameController.text));
Routes.router.navigateTo(context, "/kids/register/2",
transition: TransitionType.inFromLeft);
//Navigator.of(context).pushNamed("/kids/register/2");
},
child: new Text('Next'),
),
)
],
)
],
)));
}
@override
Widget build(BuildContext context) {
if (MyInheritedWidget.of(context).orientation == Orientation.portrait) {
numberOfHorizontalCell = 1;
} else {
numberOfHorizontalCell = 2;
}
return new Scaffold(
appBar: new AppBar(
title: new Text('Step 1: Family Details'),
),
body: _buildForm(context),
resizeToAvoidBottomPadding: true,
);
}
}
class RegisterKidPage2 extends StatefulWidget {
RegisterKidPage2({Key key}) : super(key: key);
@override
_RegisterKidPage2 createState() => new _RegisterKidPage2();
}
class _RegisterKidPage2 extends State<RegisterKidPage2> {
var numberOfHorizontalCell = 2;
var ratio = 4.5;
String radioValue = "male";
bool addressSameAsParent = false;
var _datetime = new DateTime.now();
// Age Range dropdown
dynamic ddAgeRange;
// Allergies
bool chkAllergyDairy = false;
bool chkAllergyNuts = false;
bool chkAllergyGluten = false;
// Special instructions
bool chkVegetarian = false;
bool chkBringsOwnFood = false;
void handleRadioValueChanged(String value) {
setState(() {
radioValue = value;
});
}
void handleCheckboxValueChanged(bool value) {
setState(() {
addressSameAsParent = value;
});
}
void chkAllergyDairyChanged(bool value) {
setState(() {
chkAllergyDairy = value;
});
}
void chkAllergyNutsChanged(bool value) {
setState(() {
chkAllergyNuts = value;
});
}
void chkAllergyGlutenChanged(bool value) {
setState(() {
chkAllergyGluten = value;
});
}
void chkVegetarianChanged(bool value) {
setState(() {
chkVegetarian = value;
});
}
void chkBringsOwnFoodChanged(bool value) {
setState(() {
chkBringsOwnFood = value;
});
}
void ddAgeRangeChanged(dynamic value) {
setState(() {
ddAgeRange = value;
});
}
void handleDropDownValueChanged(dynamic value) {
setState(() {});
}
void uploadPictureButton() {}
void _submit() {}
Widget _buildForm() {
return new Form(
child: new Container(
child: new Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
new Flexible(
child: new GridView.count(
crossAxisCount: numberOfHorizontalCell,
childAspectRatio: ratio,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: [
new ListTile(
title: new TextFormField(
initialValue: new StoreProvider.of(context).store.state,
decoration: new InputDecoration(
hintText: "Firstname",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Lastname",
),
),
),
new Row(
children: <Widget>[
new Flexible(
child: new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "BirthDate",
),
),
)),
new Flexible(
child: new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Birth month",
),
),
)),
new Flexible(
child: new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Birth Year",
),
),
)),
],
),
new Row(children: [
new Flexible(
child: new DropdownButton(
onChanged: ddAgeRangeChanged,
hint: new Text("zdzd"),
value: ddAgeRange,
items: [
new DropdownMenuItem(
child: new Text("2 years"),
)
]))
]),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Class Name",
),
),
),
new ListTile(
title: new TextFormField(
decoration: new InputDecoration(
hintText: "Teacher Name",
),
),
),
new Row(
children: <Widget>[
new Radio<String>(
value: "Male",
groupValue: radioValue,
onChanged: handleRadioValueChanged),
new Text("Male"),
new Divider(),
new Radio<String>(
value: "Female",
groupValue: radioValue,
onChanged: handleRadioValueChanged),
new Text("Female"),
],
),
new Row(
children: [
new Checkbox(
value: addressSameAsParent,
onChanged: handleCheckboxValueChanged),
new Text("Same address as "),
new Flexible(
child: new DropdownButton(
onChanged: handleDropDownValueChanged,
items: new List.generate(20, (int index) {
return new DropdownMenuItem(
child: new Container(
child: new Text("Item#$index"),
));
}),
),
),
],
),
new Text(
"Allergies",
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
fontWeight: FontWeight.bold, fontSize: 25.5),
),
new Row(
children: [
new Checkbox(
value: chkAllergyDairy,
onChanged: chkAllergyDairyChanged),
new Text("Dairy"),
new Checkbox(
value: chkAllergyNuts,
onChanged: chkAllergyNutsChanged),
new Text("Nuts"),
new Checkbox(
value: chkAllergyGluten,
onChanged: chkAllergyGlutenChanged),
new Text("Gluten"),
],
),
new Container(
decoration: new BoxDecoration(
border: new Border.all(color: Colors.blueAccent)),
child: new TextFormField(
decoration: new InputDecoration(
hintText:
"For EACH allergy, explain what the parent/guardian wants to do (if there is a reaction)",
),
maxLines: 5,
keyboardType: TextInputType.multiline,
),
),
new Text(
"Special Instructions",
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
fontWeight: FontWeight.bold, fontSize: 25.5),
),
new Row(
children: [
new Checkbox(
value: chkVegetarian, onChanged: chkVegetarianChanged),
new Text("Vegetarian"),
new Checkbox(
value: chkBringsOwnFood,
onChanged: chkBringsOwnFoodChanged),
new Text("Brings own food"),
],
),
new Row(
children: [
new Text(
"Picture",
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
fontWeight: FontWeight.bold, fontSize: 25.5),
),
new RaisedButton(
onPressed: uploadPictureButton,
child: new Text("Upload"))
],
),
new Container(
decoration: new BoxDecoration(
border: new Border.all(color: Colors.blueAccent)),
child: new TextFormField(
decoration: new InputDecoration(
hintText: "Notes",
),
maxLines: 6,
keyboardType: TextInputType.multiline,
),
),
new Text(
"Start Date",
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
fontWeight: FontWeight.bold, fontSize: 25.5),
),
new Row(
children: [
new Flexible(
child: new DateTimeItem(
dateTime: _datetime,
onChanged: (dateTime) =>
setState(() => _datetime = dateTime),
),
),
],
),
]),
),
new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
new Padding(
padding: const EdgeInsets.all(16.0),
child: new RaisedButton(
onPressed: _submit,
child: new Text('Done'),
),
)
],
)
],
)));
}
@override
Widget build(BuildContext context) {
if (MyInheritedWidget.of(context).orientation == Orientation.portrait) {
numberOfHorizontalCell = 1;
} else {
numberOfHorizontalCell = 2;
}
return new Scaffold(
appBar: new AppBar(
title: new Text("Step 2: Kid's Details"),
),
body: _buildForm(),
resizeToAvoidBottomPadding: true,
);
}
}
/*
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _ipAddress = 'Unknown';
_getIPAddress() async {
HttpClient client = new HttpClient();
String result;
client.getUrl(Uri.parse("http://35.205.143.130/api/v1/children"))
.then((HttpClientRequest request) {
request.headers.add("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjIzZTIyYTQ3NjZmYjYyNzMxN2ZjOGRjN2M5YjRkM2M3ZmY4YTg1NjcifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVkZHljYXJlLTE5MzkxMCIsIm5hbWUiOiJBcnRodXIgR3VzdGluIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS8tWGRVSXFkTWtDV0EvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvNDI1MnJzY2J2NU0vcGhvdG8uanBnIiwiYWRtaW4iOnRydWUsImFkdWx0IjpmYWxzZSwib2ZmaWNlbWFuYWdlciI6ZmFsc2UsInRlYWNoZXIiOmZhbHNlLCJ1c2VySWQiOiJlOTU2OWY5ZC0yMmVkLTQyOTctOTAwOC0wMzlkMTIwOWEzMmEiLCJhdWQiOiJ0ZWRkeWNhcmUtMTkzOTEwIiwiYXV0aF90aW1lIjoxNTIwMTk3ODU1LCJ1c2VyX2lkIjoid0FnVUpQQ0IzRk92aDVEck1XanZMTm45bzIwMiIsInN1YiI6IndBZ1VKUENCM0ZPdmg1RHJNV2p2TE5uOW8yMDIiLCJpYXQiOjE1MjAxOTc4NTYsImV4cCI6MTUyMDIwMTQ1NiwiZW1haWwiOiJhcnRodXIuZ3VzdGluQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7Imdvb2dsZS5jb20iOlsiMTE0MjQxNDU1NTI0MTUyMzUyMTUwIl0sImVtYWlsIjpbImFydGh1ci5ndXN0aW5AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ29vZ2xlLmNvbSJ9fQ.hEnV0_kA91dY5ZxAhbZahvPHlCGF97DCUSIc0ASUfsCzzNRg-gCL9u3AELMc6kSxVB8FYSKq_hdg7uyP8P-sSIlto4ssdkXtUWJp8DfULo2R1Ya-fPOB4bjRVGL4XiX3i2Vksq2XK6GEmHIg1vjNTtq401rdAIS7mOXcfYEu9GdPL2OyuV6EdmZvrHMuFGdlziTUiv8Xzviy1bvIxJc2cVkLyUAMOUZuT6kGBxPKtXIG6GJccH6dOL07W9NJBDaC_BS5lklt-H7lWTMsqfPcT3322oIAftGUVfjmuSAzUwPN0CID-xncD8v0eCe_doEdWlQp3LYsAVKhRRoM_4j1TQ");
// Optionally write to the request object...
// Then call close.
return request.close();
}).then((HttpClientResponse response) {
if (response.statusCode == HttpStatus.OK) {
response.transform(UTF8.decoder).listen((contents) {
// handle data
var decoded = JSON.decode(contents);
print(decoded);
var user = new Child.fromJson(decoded);
print(user);
});
}else {
result =
'Error getting children:\nHttp status ${response.statusCode}';
}
});
// If the widget was removed from the tree while the message was in flight,
// we want to discard the reply rather than calling setState to update our
// non-existent appearance.
if (!mounted) return;
setState(() {
_ipAddress = result;
});
}
@override
Widget build(BuildContext context) {
var spacer = new SizedBox(height: 32.0);
return new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('Children:'),
new Text('$_ipAddress.'),
spacer,
new RaisedButton(
onPressed: _getIPAddress,
child: new Text('Get children'),
),
],
),
);
}
}
*/
class DateTimeItem extends StatelessWidget {
DateTimeItem({Key key, DateTime dateTime, @required this.onChanged})
: assert(onChanged != null),
date = dateTime == null
? new DateTime.now()
: new DateTime(dateTime.year, dateTime.month, dateTime.day),
time = dateTime == null
? new DateTime.now()
: new TimeOfDay(hour: dateTime.hour, minute: dateTime.minute),
super(key: key);
final DateTime date;
final TimeOfDay time;
final ValueChanged<DateTime> onChanged;
@override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Expanded(
child: new InkWell(
onTap: (() => _showDatePicker(context)),
child: new ListTile(
title: new Text(new DateFormat('EEEE, MMMM d').format(date)),
leading: new IconButton(
icon: new Icon(Icons.calendar_today),
onPressed: () {},
),
),
),
),
/* new InkWell(
onTap: (() => _showTimePicker(context)),
child: new Padding(
padding: new EdgeInsets.symmetric(vertical: 8.0),
child: new Text('$time')),
),*/
],
);
}
Future _showDatePicker(BuildContext context) async {
DateTime dateTimePicked = await showDatePicker(
context: context,
initialDate: date,
firstDate: date.subtract(const Duration(days: 20000)),
lastDate: new DateTime.now());
if (dateTimePicked != null) {
onChanged(new DateTime(dateTimePicked.year, dateTimePicked.month,
dateTimePicked.day, time.hour, time.minute));
}
}
Future _showTimePicker(BuildContext context) async {
TimeOfDay timeOfDay =
await showTimePicker(context: context, initialTime: time);
if (timeOfDay != null) {
onChanged(new DateTime(
date.year, date.month, date.day, timeOfDay.hour, timeOfDay.minute));
}
}
}
from flutter_redux.
Thanks @brianegan ! I can't test now since I'm at work but I think I understand what the solution is.
I have another question and it's related to this topic so I don't want to open another issue.
How would you handle all the fields in the form ? My idea is that (correct me if I'm wrong):
- 1 global store for all my application
- 1 reducer per use case: in my poc I want to store all the informations in both pages, then wrap it up and send a json request
- The reducer needs to update fields in a specific class ? Or maybe it's inside the action like the following code ?
class MyAction {
final String firstName;
final String lastName;
final String adress_1;
...
MyAction(this.firstName, this.lastName, this.adress_1);
}
Once I understand this I will have a cleaning phase of those 500 lines, I promise !
from flutter_redux.
Or the state of the store would be my class embedding all my attributes ?
from flutter_redux.
- Yep, 1 global store for your application!
- The Reducer should intercept actions and update the state of the app.
- Actions Send data to the Reducer / Middleware layer.
In general, I'd probably start with this approach and see if you run into problems: https://github.com/brianegan/flutter_architecture_samples/blob/master/example/redux/lib/presentation/add_edit_screen.dart#L40
Basically, in this code:
- Create text controllers for each textformfield in your form on Screen 1, as well as necessary validation logic
- On the "next" button, validate the form. If it's valid, dispatch an action with the form data and navigate to the next screen.
- In your Reducer, intercept this action and update the app state.
- Your Second Screen will launch. You can pull the current values you need from the Store and set the
initialValue
of the TextFormField. - Validate and Dispatch on the Second Screen
- Write a Middleware function that intercepts the action from the second screen, it will call your API with all the necessary data.
Hope that helps!
from flutter_redux.
Thinking about this a bit more, if you only need redux for this use-case, might be easier to just work with an InheritedWidget
. You could follow the same general pattern, but rather than dispatching actions and all that, you could just set fields on your InheritedWidget
.
from flutter_redux.
@arthurgustin Anything else I can help with here? Cool to close?
from flutter_redux.
Hi @brianegan,
I made something working, but I could not get rid of text controllers inside the form because when I switch page or I scroll, widgets get cleared because they are redraw (this is what I've understand by looking at forums).
In the end, there are a lot of boilerplate, but it works. You can close this issue :) But I will open another because I have another question !
from flutter_redux.
Related Issues (20)
- How to not accept change notifications when the Router is not at the top level, and then take the initiative to obtain the changes when the Router returns to the top level HOT 1
- onDidChange called twice HOT 8
- Is this package still actively maintained?
- `onWillChange` and `onDidChange` errors are swallowed HOT 4
- Warning related to `?` operator on Linux/Dev channel. HOT 2
- Import Store data type
- Widgets binding error after upgrading flutter version to 2.13.0-0.1.pre HOT 1
- onDidChange not working as expected HOT 3
- Build warning with Flutter 3.0 HOT 5
- flutter_redux depends on flutter_redux, version solving failed HOT 3
- onWillChange and onDidChange called twice after state update HOT 2
- Passing Store as an argument to MethodChannel and Background Isolate
- Question on how to use the reducer right way HOT 2
- [Question]: Any thoughts on difference between ignoreChange and distinct?
- Socket and Flutter_redux HOT 18
- Dispatching an action that does not alter state rerenders the widget, causing an infinite loop HOT 5
- How to watch redux store change inside useEffect? HOT 5
- [Question] is it expected that the store doesn't run until frames render? HOT 1
- After push a new page, and Dispatching an action, there is an infinite loop
- Is flutter redux actively maintained HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from flutter_redux.