I am developing an app that is counting points, after the teams have been created. After the teams are created, an AlertDialog pops up and displays the names. Then it should be possible to click on a button to open a new activity. That activity should not be connected to the previous activity. Does anybody has an idea, how this could be done?
Here is the code-snippet of the dialog activity:
import 'dart:math';
import 'package:flutter/material.dart';
import 'punktezaehler.dart';
class Team_Dialog extends StatefulWidget{
final List<String> team_namen;
Team_Dialog(this.team_namen);
#override
State<StatefulWidget> createState() => new _TeamDialogState(team_namen);
}
class _TeamDialogState extends State<Team_Dialog>{
final List<String> team_namen;
_TeamDialogState(this.team_namen);
#override
Widget build(BuildContext context) {
return new AlertDialog(
content: new SingleChildScrollView(
child: new ListBody(
children: List.generate(1, (index){
return Column(
children: <Widget>[
new Row(
children: <Widget>[
Text("Team 1: ", style: TextStyle(fontFamily: "Roboto")),
Text(team_namen[0] + " und " + team_namen[1])
],
),
new Row(
children: <Widget>[
Text("Team 2: "),
Text(team_namen[2] + " und " + team_namen[3])
],
)
],
);
})
),
),
actions: <Widget>[
new FlatButton(
color: Colors.red,
splashColor: Colors.red[900],
onPressed: (){
Navigator.of(context).pop();
},
child: new Text("Abort", style: TextStyle(color: Colors.white),)
),
new IconButton(
icon: Icon(Icons.shuffle),
onPressed: (){
shuffle(team_namen);
setState(() {
});
}
),
new FlatButton(
color: Colors.green,
splashColor: Colors.green[800],
onPressed: () => , //After click it should start new Activity
child: new Text("Start Game", style: TextStyle(color: Colors.white))
)
],
);
}
List shuffle(List items) {
var random = new Random();
for (var i = items.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = items[i];
items[i] = items[n];
items[n] = temp;
}
return items;
}
}
It would be awesome if someone has an idea :D
Actually when you are talking about Flutter think about pages, not activities. It should be something like :
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondScreen()),);
SecondScreen is another widget with its own Widget build(BuildContext context) method where you will declare what to have on this page.
In case you want to return back, you can do it with:
Navigator.pop(context);
Source documentation
You can also use named routes for navigation. Example:
MaterialApp(
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
);
And something like:
Navigator.pushNamed(context, '/second');
Documentation
It's not a good practice to use Navigator.push() or Navigator.pushNamed() and then remove the back button. Because the page that you are leaving from will remain in the pages stack.
What you actually should use is Navigator.pushReplacement() if you don't want the user to be able to go back to the previous page.
And if you are doing it from a dialog, you should pop the dialog first and then push the next page.
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
I'm trying to change my icon after I tap on my List Item. I already tried different things: I tried the onTap method but the icon just does not want to change. I'm very new to flutter and I would love to find some help for my problem :). Here is my code.
I already searched for solutions but I didn't got it working in my project
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'To-Do List',
theme: ThemeData(
primaryColor: Colors.white,
brightness: Brightness.dark,
),
home: Scaffold(
appBar: AppBar(title: Text('To-Do List'),
backgroundColor: Colors.amber,
),
body: BodyLayout(),
),
);
}
}
class BodyLayout extends StatefulWidget {
#override
BodyLayoutState createState() {
return new BodyLayoutState();
}
}
class BodyLayoutState extends State<BodyLayout> {
// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
// backing data
List<String> _data = [];
final _isdone = Set<String>();
// bool selected = false;
List<bool> selected = new List<bool>();
Icon notdone = Icon(Icons.check_box_outline_blank);
Icon done = Icon(Icons.check_box);
TextEditingController todoController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 445,
child: AnimatedList(
// Give the Animated list the global key
key: _listKey,
initialItemCount: _data.length,
// Similar to ListView itemBuilder, but AnimatedList has
// an additional animation parameter.
itemBuilder: (context, index, animation) {
// Breaking the row widget out as a method so that we can
// share it with the _removeSingleItem() method.
return _buildItem(_data[index], animation);
},
),
),
TextField(
controller: todoController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'To-Do'
),
),
RaisedButton(
child: Text('Insert item', style: TextStyle(fontSize: 20)),
onPressed: () {
_insertSingleItem();
},
),
RaisedButton(
child: Text('Remove item', style: TextStyle(fontSize: 20)),
onPressed: () {
_removeSingleItem();
},
)
],
);
}
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: Icon(
isdone ? Icons.check_box: Icons.check_box_outline_blank
),
onTap: (){
setState(() {
});
},
),
),
);
}
void _insertSingleItem() {
int insertIndex = 0;
setState(() {
_data.insert(0, todoController.text);
});
// Add the item to the data list.
// Add the item visually to the AnimatedList.
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItem() {
int removeIndex = 0;
// Remove item from data list but keep copy to give to the animation.
String removedItem = _data.removeAt(removeIndex);
// This builder is just for showing the row while it is still
// animating away. The item is already gone from the data list.
AnimatedListRemovedItemBuilder builder = (context, animation) {
return _buildItem(removedItem, animation);
};
// Remove the item visually from the AnimatedList.
_listKey.currentState.removeItem(removeIndex, builder);
}
}```
You have already mentioned the icons above. You simply need to use them instead of declaring new ones again.
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: isdone ? done: notdone, // use the icon variables you have already defined
onTap: (){
setState(() {
// add the item to _isdone set if it is not added and remove it if it is added when tapped on the list item
if(isdone) {
_isdone.remove(item);
} else {
_isdone.add(item);
}
});
},
),
),
);
}
In this code, I have added the item and removed the item in setSate() in the onTap(), so that whenever you tap the list item, _isdone Set gets updated and the build() is reloaded. Which makes your layout and data update itself every time you tap on the list item.
I am flutter beginner.
I have my main page split into 2 custom widgets, top widget and page widget. And I have a drawer with a few buttons to change the content of the page widget.
I followed this tutorial and try to swap the widget as per click on the buttons.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var currentpage = 'landing';
Widget pageWidget;
switch (currentpage) {
case 'landing':
pageWidget = new Landing();
break;
case 'visitors':
pageWidget = new Visitors();
break;
default:
}
return Scaffold(
body:new Container(
child: Container(
child: Column(
children: <Widget>[
Header(), ///<-- widget declared in separated .dart file
pageWidget
],
)
)
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text(
"UserN3",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black54),
),
),
new GestureDetector(
onTap: (){
print("landing");
Future.delayed(Duration.zero,(){
setState(() {
currentpage = 'landing';
});
});
},
child: new Text("landing"),
),
new GestureDetector(
onTap: (){
print("visitors");
Future.delayed(Duration.zero,(){
setState(() {
currentpage = 'visitors';
});
});
},
child: new Text("Visitors"),
),
],
),
),
);
}
so the gist is:
I have switch case in the build, which will change the pageWidget accordingly based on currentpage, which will be changed by buttons in the drawer.
But keep getting the same error:
Another exception was thrown: setState() or markNeedsBuild() called when widget tree was locked.
As you can see I added Future.delayed() based on this suggestion but the error persist.
After some researching, I got a feeling that it related to how drawer deal with setState(), but I am not sure how.
Obviously I still not getting how flutter work in terms of build and state.
What seems to be the problem and how can I resolve this?
You should declare your currentPage variable outside your build method, because every time you call setState the build method is called again and the variable doesn't change.
So move this :
Widget build(BuildContext context) {
var currentpage = 'landing';
To this:
var currentpage = 'landing';
Widget build(BuildContext context) {
I'm new in Flutter.
I have a question
How to call layouts in flutter ?
I've been create some layouts that contains a lot of widget.
It's not right if I make every code inside 1 file.
so I decide to put the code for the widgets in every 1 layouts file.
and I dont know how to call them in the home-page.dart that I create.
I mean, if I push THIS (i.e page1.dart), then the page1.dart is appear.
thought that file (page1.dart) is in other directory (not inside lib dir).
I dont know. am I should use ROUTES ?
but I dont know how.
would you like to teach me ?
..............
here are. I have TabBar like this in my home_page.dart:
import 'package:flutter/material.dart';
import 'package:coba/second.dart';
class HomePage extends StatelessWidget {
static String tag = 'home-page';
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
title: "MySampleApplication",
home: new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Hello Flutter App"),
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: "First Tab"),
new Tab(text: "Second Tab"),
new Tab(text: "Third Tab"),
],
),
),
body: new TabBarView(
children: <Widget>[
new Text("You've Selected First"),
new SecondWidget(),
new ThirdWidget(),
]
)
),
)
);
}
}
class SecondWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
second(data: 'Hello there from the first page!'),
),
}
}
class ThirdWidget extends StatelessWidget {
#override
Widget build(BuildContext ctxt) {
return new Column(
children: <Widget>[
Text('halooo'),
Container(
color: Colors.black,
width: 200,
height: 200,
)
],
);
}
}
thank you so much
You can use any name that you want (generally, we have seen xxxScreen.dart or xxxPage.dart, but it is totally up to you).
Import your "destiny" page using in "origin" page using import:
import 'package:myproject/myPageScreen.dart';
Flutter offers 3 options:
Using Navigator:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
SecondPage(data: 'Hello there from the first page!'),
),
Using Named routes:
Declare you routes in MaterialApp:
MaterialApp(
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
);
And then use named route with Navigator:
onPressed: () {
Navigator.pushNamed(context, '/second');
}
Using onGenerateRoute:
Declare this property on your MaterialApp:
return MaterialApp(
// Initially display FirstPage
initialRoute: '/',
onGenerateRoute: _getRoute,
);
And create your route generator :
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) =>
FirstPage());
case '/second':
// Validation of correct data type
if (args is String) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
}
You can create your router as another file to help to organize your project.
in the Codelab English words example...
https://flutter.io/get-started/codelab/
The iOS Navigation transition is horizontal.. as you would expect a Segue to act in a UINavigationController. Right to left... Pops are left to right.
ANDROID, the same example is VERTICAL, Bottom to Top. Pops are Top to bottom.
MY QUESTION... how would I force a Horizontal transition in ANDROID so it behaves like iOS? I suspect I will have to use MaterialPageRoute
/*
Nguyen Duc Hoang(Mr)
Programming tutorial channel:
https://www.youtube.com/c/nguyenduchoang
Flutter, React, React Native, IOS development, Swift, Python, Angular
* */
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
//Define "root widget"
void main() => runApp(new MyApp());//one-line function
//StatefulWidget
class RandomEnglishWords extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new RandomEnglishWordsState();//return a state's object. Where is the state's class ?
}
}
//State
class RandomEnglishWordsState extends State<RandomEnglishWords>{
final _words = <WordPair>[];//Words displayed in ListView, 1 row contains 1 word
final _checkedWords = new Set<WordPair>();//set contains "no duplicate items"
#override
Widget build(BuildContext context) {
// TODO: implement build
//Now we replace this with a Scaffold widget which contains a ListView
return new Scaffold(
appBar: new AppBar(
title: new Text("List of English words"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list),
onPressed: _pushToSavedWordsScreen)
],
),
body: new ListView.builder(itemBuilder: (context, index) {
//This is an anonymous function
//index = 0, 1, 2, 3,...
//This function return each Row = "a Widget"
if (index >= _words.length) {
_words.addAll(generateWordPairs().take(10));
}
return _buildRow(_words[index], index);//Where is _buildRow ?
}),
);
}
_pushToSavedWordsScreen() {
// print("You pressed to the right Icon");
//To navigate, you must have a "route"
final pageRoute = new MaterialPageRoute(builder: (context) {
//map function = Convert this list to another list(maybe different object's type)
//_checkedWords(list of WordPair) => map =>
// converted to a lazy list(Iterable) of ListTile
final listTiles = _checkedWords.map( (wordPair) {
return new ListTile(
title: new Text(wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(children: listTiles.toList(),),//Lazy list(Iterable) => List
);
});
Navigator.of(context).push(pageRoute);
}
Widget _buildRow(WordPair wordPair, int index) {
//This widget is for each row
final textColor = index % 2 == 0 ? Colors.red : Colors.blue;
final isChecked = _checkedWords.contains(wordPair);
return new ListTile(
//leading = left, trailing = right. Is is correct ? Not yet
leading: new Icon(
isChecked ? Icons.check_box : Icons.check_box_outline_blank,
color: textColor,
),
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 18.0, color: textColor),
),
onTap: () {
setState(() {
//This is an anonymous function
if (isChecked) {
_checkedWords.remove(wordPair);//Remove item in a Set
} else {
_checkedWords.add(wordPair);//Add item to a Set
}
});
},
);
}
}
class MyApp extends StatelessWidget {
//Stateless = immutable = cannot change object's properties
//Every UI components are widgets
#override
Widget build(BuildContext context) {
//build function returns a "Widget"
return new MaterialApp(
title: "This is my first Flutter App",
home: new RandomEnglishWords()
);//Widget with "Material design"
}
}
First of all about MaterialPageRoute does not help with your case. Here is the official explanation for it:
The MaterialPageRoute is handy because it transitions to the new
screen using a platform-specific animation.
And those animations you see are the platform-specific animations.
If you want to implement a custom animation, you need to implement it manually by using PageRouteBuilder. Here is how you can do it.
Here is a modified version of your _pushToSavedWordsScreen which does the right to left transition. Tested on Google Pixel.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
final listTiles = _checkedWords.map((wordPair) {
return new ListTile(
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(
children: listTiles.toList(),
), //Lazy list(Iterable) => List
);
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
This is the modified code for the new Navigation. Android and iOS both Navigate Horizontally.
Origianl code: https://flutter.io/get-started/codelab/[1]
pubspec.yaml... Don't forget to add :
dependencies:
flutter:
sdk: flutter
english_words: ^3.1.0 #version 3.1.0 or above
UPDATED CODE: main.dart.
/*
Nguyen Duc Hoang(Mr)
Programming tutorial channel:
https://www.youtube.com/c/nguyenduchoang
Flutter, React, React Native, IOS development, Swift, Python, Angular
* */
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
//Define "root widget"
void main() => runApp(new MyApp());//one-line function
//StatefulWidget
class RandomEnglishWords extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new RandomEnglishWordsState();//return a state's object. Where is the state's class ?
}
}
//State
class RandomEnglishWordsState extends State<RandomEnglishWords> {
final _words = <WordPair>[
]; //Words displayed in ListView, 1 row contains 1 word
final _checkedWords = new Set<WordPair>(); //set contains "no duplicate items"
#override
Widget build(BuildContext context) {
// TODO: implement build
//Now we replace this with a Scaffold widget which contains a ListView
return new Scaffold(
appBar: new AppBar(
title: new Text("List of English words"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list),
onPressed: _pushToSavedWordsScreen)
],
),
body: new ListView.builder(itemBuilder: (context, index) {
//This is an anonymous function
//index = 0, 1, 2, 3,...
//This function return each Row = "a Widget"
if (index >= _words.length) {
_words.addAll(generateWordPairs().take(10));
}
return _buildRow(_words[index], index); //Where is _buildRow ?
}),
);
}
_pushToSavedWordsScreen() {
// print("You pressed to the right Icon");
//To navigate, you must have a "route"
//======================================================================
//======= original solution - ANDROID transitions Vertically
// final pageRoute = new MaterialPageRoute(builder: (context) {
// //map function = Convert this list to another list(maybe different object's type)
// //_checkedWords(list of WordPair) => map =>
// // converted to a lazy list(Iterable) of ListTile
// final listTiles = _checkedWords.map( (wordPair) {
// return new ListTile(
// title: new Text(wordPair.asUpperCase,
// style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
// );
// });
// //Now return a widget, we choose "Scaffold"
// return new Scaffold(
// appBar: new AppBar(
// title: new Text("Checked words"),
// ),
// body: new ListView(children: listTiles.toList(),),//Lazy list(Iterable) => List
// );
// });
// Navigator.of(context).push(pageRoute);
// }
//=========== OLD solution... ANDROID transitions Vertically
//==================================================================
//==================================================================
//=========== new solution... transition Horizontal
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
final listTiles = _checkedWords.map((wordPair) {
return new ListTile(
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(
children: listTiles.toList(),
), //Lazy list(Iterable) => List
);
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
}
//========= end of solution
//=============================================================
Widget _buildRow(WordPair wordPair, int index) {
//This widget is for each row
final textColor = index % 2 == 0 ? Colors.red : Colors.blue;
final isChecked = _checkedWords.contains(wordPair);
return new ListTile(
//leading = left, trailing = right. Is is correct ? Not yet
leading: new Icon(
isChecked ? Icons.check_box : Icons.check_box_outline_blank,
color: textColor,
),
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 18.0, color: textColor),
),
onTap: () {
setState(() {
//This is an anonymous function
if (isChecked) {
_checkedWords.remove(wordPair); //Remove item in a Set
} else {
_checkedWords.add(wordPair); //Add item to a Set
}
});
},
);
}
}
class MyApp extends StatelessWidget {
//Stateless = immutable = cannot change object's properties
//Every UI components are widgets
#override
Widget build(BuildContext context) {
//build function returns a "Widget"
return new MaterialApp(
title: "This is my first Flutter App",
home: new RandomEnglishWords()
);//Widget with "Material design"
}
}