Flutter async convention/best practices for State and Widget
up vote
0
down vote
favorite
Coding with async/await/... can be done in many different ways. Is there an official pattern/convention/set of best practices?
In the code example below I have used (inconsistently) State related calls, data flags to drive Widget behaviours...(I didn't use FutureBuilder or StreamBuilder as explained in this post Flutter: Best Practices of Calling Async Codes from UI to focus on other ways).
All these different ways technically work but the code can become messy and bad for maintenance/team work. Any guidelines/convention on how to do it?
3 questions in the comments of the code...showing it all as I have also seen some people struggling with making it work (in case it helps)
//Don't forget the dependencies in the pubspec.yaml
//dependencies:
// flutter:
// sdk: flutter
// sqflite: any
// path_provider: any
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//for the DB
final String tblUsers = "users";
final String colUserid = "userid";
final String colDisplayName = "displayName";
//nothing interesting until the next comment
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
//Focus for the discussion
class _MyHomePageState extends State<MyHomePage> {
DataBroker data;
bool dataLoaded=false;
//These are just to play with async data fetching/loading...no functional need for that
User user;
User userTwo;
_MyHomePageState();
//I want my database to be ready before I do anything...so I put all the init data at the beginning
@override
void initState() {
super.initState();
//Calling a function with all the sequential data fetching I need to init
//this doesn't prevent the UI from being created so some objects will be null until populated
initData().then((result) {
setState(() {
//I now know that the db has been initialised
dataLoaded=true;
});
});
}
Future<void> initData() async {
data = DataBroker();
//The await ensures that I don't fetch User data before the db has been initialised
await data.initialise();
//the await ensures I have user set before I assign it to userTwo
await data.getUser().then((u) {user=u;});
//Question 1: is it better to do it here
//Or put the 'userTwo = user' in the then()?
userTwo = user;
}
//No functional sense, i basically just want to get 'user' to be one step ahead of 'userTwo'
void _updateUser() {
user.displayName = user.displayName + '+';
data.getUser().then((res) {userTwo=res;});
data.updateUser(user).then((res){
//Empty function, just triggering setState to rebuild the UI
//Question 2: I am setting variables outside setState...should I be doing it another way?
setState(() {});
});
}
//Yet another pattern, here I directly create a function to display what I want while waiting for flags to be set
//Question 3: if I do that, I will end up with a lot of mini functions to show Widgets in 2 different cases
Widget _dataStatusWidget() {
if(dataLoaded) {
return Text('Data has been loaded');
} else {
return Text('Data is loading please wait...');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_dataStatusWidget(),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'User: ' + user.toString(),
),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'UserTwo: ' + userTwo.toString(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateUser,
tooltip: 'update user',
child: Icon(Icons.update),
),
);
}
}
//simple data access class...nothing amazing
class DataBroker {
static final DataBroker _databroker = new DataBroker._internal();
static final _dbName = "dbtest.db";
static Database _db;
DataBroker._internal();
factory DataBroker() {
return _databroker;
}
Database get db => _db;
Future<User> getUser() async {
List<Map> maps = await _db.query(
tblUsers,
columns: [colUserid, colDisplayName],
);
if (maps.length > 0) {
return new User.fromMap(maps.first);
}
return null;
}
Future<int> updateUser(User user) async {
return await _db.update(tblUsers, user.toMap(),
where: "$colUserid = ?", whereArgs: [user.userid]);
}
Future<void> initialise() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + _dbName;
await deleteDatabase(path);
_db = await openDatabase(path, version: 1, onCreate:_createDB);
}
}
//Creating the db with one user by default for simplicity
Future<Database> _createDB(Database db, int newVersion) async {
await db.execute('''
create table $tblUsers (
$colUserid integer primary key autoincrement,
$colDisplayName text
)''');
User user = User.withIDs(1,'John Doe');
await db.insert(tblUsers, user.toMap());
//just to be slow
await Future.delayed(Duration(milliseconds: 2000), () {});
return db;
}
//data class for completeness...nothing amazing
class User {
int _userid;
String _displayName;
User(this._displayName);
User.withIDs(this._userid, this._displayName);
int get userid => _userid;
String get displayName => _displayName;
set displayName(String displayName) {
_displayName = displayName;
}
String toString() {
return
"userid: " + _userid.toString() + "n" +
"displayName: " + _displayName + "n";
}
User.fromMap(dynamic o) {
this._userid=o["userid"];
this._displayName=o["displayName"];
}
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (_userid != null) {
map["userid"] = _userid;
}
map["displayName"] = _displayName;
return map;
}
}
flutter conventions
add a comment |
up vote
0
down vote
favorite
Coding with async/await/... can be done in many different ways. Is there an official pattern/convention/set of best practices?
In the code example below I have used (inconsistently) State related calls, data flags to drive Widget behaviours...(I didn't use FutureBuilder or StreamBuilder as explained in this post Flutter: Best Practices of Calling Async Codes from UI to focus on other ways).
All these different ways technically work but the code can become messy and bad for maintenance/team work. Any guidelines/convention on how to do it?
3 questions in the comments of the code...showing it all as I have also seen some people struggling with making it work (in case it helps)
//Don't forget the dependencies in the pubspec.yaml
//dependencies:
// flutter:
// sdk: flutter
// sqflite: any
// path_provider: any
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//for the DB
final String tblUsers = "users";
final String colUserid = "userid";
final String colDisplayName = "displayName";
//nothing interesting until the next comment
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
//Focus for the discussion
class _MyHomePageState extends State<MyHomePage> {
DataBroker data;
bool dataLoaded=false;
//These are just to play with async data fetching/loading...no functional need for that
User user;
User userTwo;
_MyHomePageState();
//I want my database to be ready before I do anything...so I put all the init data at the beginning
@override
void initState() {
super.initState();
//Calling a function with all the sequential data fetching I need to init
//this doesn't prevent the UI from being created so some objects will be null until populated
initData().then((result) {
setState(() {
//I now know that the db has been initialised
dataLoaded=true;
});
});
}
Future<void> initData() async {
data = DataBroker();
//The await ensures that I don't fetch User data before the db has been initialised
await data.initialise();
//the await ensures I have user set before I assign it to userTwo
await data.getUser().then((u) {user=u;});
//Question 1: is it better to do it here
//Or put the 'userTwo = user' in the then()?
userTwo = user;
}
//No functional sense, i basically just want to get 'user' to be one step ahead of 'userTwo'
void _updateUser() {
user.displayName = user.displayName + '+';
data.getUser().then((res) {userTwo=res;});
data.updateUser(user).then((res){
//Empty function, just triggering setState to rebuild the UI
//Question 2: I am setting variables outside setState...should I be doing it another way?
setState(() {});
});
}
//Yet another pattern, here I directly create a function to display what I want while waiting for flags to be set
//Question 3: if I do that, I will end up with a lot of mini functions to show Widgets in 2 different cases
Widget _dataStatusWidget() {
if(dataLoaded) {
return Text('Data has been loaded');
} else {
return Text('Data is loading please wait...');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_dataStatusWidget(),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'User: ' + user.toString(),
),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'UserTwo: ' + userTwo.toString(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateUser,
tooltip: 'update user',
child: Icon(Icons.update),
),
);
}
}
//simple data access class...nothing amazing
class DataBroker {
static final DataBroker _databroker = new DataBroker._internal();
static final _dbName = "dbtest.db";
static Database _db;
DataBroker._internal();
factory DataBroker() {
return _databroker;
}
Database get db => _db;
Future<User> getUser() async {
List<Map> maps = await _db.query(
tblUsers,
columns: [colUserid, colDisplayName],
);
if (maps.length > 0) {
return new User.fromMap(maps.first);
}
return null;
}
Future<int> updateUser(User user) async {
return await _db.update(tblUsers, user.toMap(),
where: "$colUserid = ?", whereArgs: [user.userid]);
}
Future<void> initialise() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + _dbName;
await deleteDatabase(path);
_db = await openDatabase(path, version: 1, onCreate:_createDB);
}
}
//Creating the db with one user by default for simplicity
Future<Database> _createDB(Database db, int newVersion) async {
await db.execute('''
create table $tblUsers (
$colUserid integer primary key autoincrement,
$colDisplayName text
)''');
User user = User.withIDs(1,'John Doe');
await db.insert(tblUsers, user.toMap());
//just to be slow
await Future.delayed(Duration(milliseconds: 2000), () {});
return db;
}
//data class for completeness...nothing amazing
class User {
int _userid;
String _displayName;
User(this._displayName);
User.withIDs(this._userid, this._displayName);
int get userid => _userid;
String get displayName => _displayName;
set displayName(String displayName) {
_displayName = displayName;
}
String toString() {
return
"userid: " + _userid.toString() + "n" +
"displayName: " + _displayName + "n";
}
User.fromMap(dynamic o) {
this._userid=o["userid"];
this._displayName=o["displayName"];
}
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (_userid != null) {
map["userid"] = _userid;
}
map["displayName"] = _displayName;
return map;
}
}
flutter conventions
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
Coding with async/await/... can be done in many different ways. Is there an official pattern/convention/set of best practices?
In the code example below I have used (inconsistently) State related calls, data flags to drive Widget behaviours...(I didn't use FutureBuilder or StreamBuilder as explained in this post Flutter: Best Practices of Calling Async Codes from UI to focus on other ways).
All these different ways technically work but the code can become messy and bad for maintenance/team work. Any guidelines/convention on how to do it?
3 questions in the comments of the code...showing it all as I have also seen some people struggling with making it work (in case it helps)
//Don't forget the dependencies in the pubspec.yaml
//dependencies:
// flutter:
// sdk: flutter
// sqflite: any
// path_provider: any
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//for the DB
final String tblUsers = "users";
final String colUserid = "userid";
final String colDisplayName = "displayName";
//nothing interesting until the next comment
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
//Focus for the discussion
class _MyHomePageState extends State<MyHomePage> {
DataBroker data;
bool dataLoaded=false;
//These are just to play with async data fetching/loading...no functional need for that
User user;
User userTwo;
_MyHomePageState();
//I want my database to be ready before I do anything...so I put all the init data at the beginning
@override
void initState() {
super.initState();
//Calling a function with all the sequential data fetching I need to init
//this doesn't prevent the UI from being created so some objects will be null until populated
initData().then((result) {
setState(() {
//I now know that the db has been initialised
dataLoaded=true;
});
});
}
Future<void> initData() async {
data = DataBroker();
//The await ensures that I don't fetch User data before the db has been initialised
await data.initialise();
//the await ensures I have user set before I assign it to userTwo
await data.getUser().then((u) {user=u;});
//Question 1: is it better to do it here
//Or put the 'userTwo = user' in the then()?
userTwo = user;
}
//No functional sense, i basically just want to get 'user' to be one step ahead of 'userTwo'
void _updateUser() {
user.displayName = user.displayName + '+';
data.getUser().then((res) {userTwo=res;});
data.updateUser(user).then((res){
//Empty function, just triggering setState to rebuild the UI
//Question 2: I am setting variables outside setState...should I be doing it another way?
setState(() {});
});
}
//Yet another pattern, here I directly create a function to display what I want while waiting for flags to be set
//Question 3: if I do that, I will end up with a lot of mini functions to show Widgets in 2 different cases
Widget _dataStatusWidget() {
if(dataLoaded) {
return Text('Data has been loaded');
} else {
return Text('Data is loading please wait...');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_dataStatusWidget(),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'User: ' + user.toString(),
),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'UserTwo: ' + userTwo.toString(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateUser,
tooltip: 'update user',
child: Icon(Icons.update),
),
);
}
}
//simple data access class...nothing amazing
class DataBroker {
static final DataBroker _databroker = new DataBroker._internal();
static final _dbName = "dbtest.db";
static Database _db;
DataBroker._internal();
factory DataBroker() {
return _databroker;
}
Database get db => _db;
Future<User> getUser() async {
List<Map> maps = await _db.query(
tblUsers,
columns: [colUserid, colDisplayName],
);
if (maps.length > 0) {
return new User.fromMap(maps.first);
}
return null;
}
Future<int> updateUser(User user) async {
return await _db.update(tblUsers, user.toMap(),
where: "$colUserid = ?", whereArgs: [user.userid]);
}
Future<void> initialise() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + _dbName;
await deleteDatabase(path);
_db = await openDatabase(path, version: 1, onCreate:_createDB);
}
}
//Creating the db with one user by default for simplicity
Future<Database> _createDB(Database db, int newVersion) async {
await db.execute('''
create table $tblUsers (
$colUserid integer primary key autoincrement,
$colDisplayName text
)''');
User user = User.withIDs(1,'John Doe');
await db.insert(tblUsers, user.toMap());
//just to be slow
await Future.delayed(Duration(milliseconds: 2000), () {});
return db;
}
//data class for completeness...nothing amazing
class User {
int _userid;
String _displayName;
User(this._displayName);
User.withIDs(this._userid, this._displayName);
int get userid => _userid;
String get displayName => _displayName;
set displayName(String displayName) {
_displayName = displayName;
}
String toString() {
return
"userid: " + _userid.toString() + "n" +
"displayName: " + _displayName + "n";
}
User.fromMap(dynamic o) {
this._userid=o["userid"];
this._displayName=o["displayName"];
}
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (_userid != null) {
map["userid"] = _userid;
}
map["displayName"] = _displayName;
return map;
}
}
flutter conventions
Coding with async/await/... can be done in many different ways. Is there an official pattern/convention/set of best practices?
In the code example below I have used (inconsistently) State related calls, data flags to drive Widget behaviours...(I didn't use FutureBuilder or StreamBuilder as explained in this post Flutter: Best Practices of Calling Async Codes from UI to focus on other ways).
All these different ways technically work but the code can become messy and bad for maintenance/team work. Any guidelines/convention on how to do it?
3 questions in the comments of the code...showing it all as I have also seen some people struggling with making it work (in case it helps)
//Don't forget the dependencies in the pubspec.yaml
//dependencies:
// flutter:
// sdk: flutter
// sqflite: any
// path_provider: any
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
//for the DB
final String tblUsers = "users";
final String colUserid = "userid";
final String colDisplayName = "displayName";
//nothing interesting until the next comment
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
//Focus for the discussion
class _MyHomePageState extends State<MyHomePage> {
DataBroker data;
bool dataLoaded=false;
//These are just to play with async data fetching/loading...no functional need for that
User user;
User userTwo;
_MyHomePageState();
//I want my database to be ready before I do anything...so I put all the init data at the beginning
@override
void initState() {
super.initState();
//Calling a function with all the sequential data fetching I need to init
//this doesn't prevent the UI from being created so some objects will be null until populated
initData().then((result) {
setState(() {
//I now know that the db has been initialised
dataLoaded=true;
});
});
}
Future<void> initData() async {
data = DataBroker();
//The await ensures that I don't fetch User data before the db has been initialised
await data.initialise();
//the await ensures I have user set before I assign it to userTwo
await data.getUser().then((u) {user=u;});
//Question 1: is it better to do it here
//Or put the 'userTwo = user' in the then()?
userTwo = user;
}
//No functional sense, i basically just want to get 'user' to be one step ahead of 'userTwo'
void _updateUser() {
user.displayName = user.displayName + '+';
data.getUser().then((res) {userTwo=res;});
data.updateUser(user).then((res){
//Empty function, just triggering setState to rebuild the UI
//Question 2: I am setting variables outside setState...should I be doing it another way?
setState(() {});
});
}
//Yet another pattern, here I directly create a function to display what I want while waiting for flags to be set
//Question 3: if I do that, I will end up with a lot of mini functions to show Widgets in 2 different cases
Widget _dataStatusWidget() {
if(dataLoaded) {
return Text('Data has been loaded');
} else {
return Text('Data is loading please wait...');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_dataStatusWidget(),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'User: ' + user.toString(),
),
//This will show null in the beginning as they are going to be displayed before the data is available
Text(
'UserTwo: ' + userTwo.toString(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateUser,
tooltip: 'update user',
child: Icon(Icons.update),
),
);
}
}
//simple data access class...nothing amazing
class DataBroker {
static final DataBroker _databroker = new DataBroker._internal();
static final _dbName = "dbtest.db";
static Database _db;
DataBroker._internal();
factory DataBroker() {
return _databroker;
}
Database get db => _db;
Future<User> getUser() async {
List<Map> maps = await _db.query(
tblUsers,
columns: [colUserid, colDisplayName],
);
if (maps.length > 0) {
return new User.fromMap(maps.first);
}
return null;
}
Future<int> updateUser(User user) async {
return await _db.update(tblUsers, user.toMap(),
where: "$colUserid = ?", whereArgs: [user.userid]);
}
Future<void> initialise() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + _dbName;
await deleteDatabase(path);
_db = await openDatabase(path, version: 1, onCreate:_createDB);
}
}
//Creating the db with one user by default for simplicity
Future<Database> _createDB(Database db, int newVersion) async {
await db.execute('''
create table $tblUsers (
$colUserid integer primary key autoincrement,
$colDisplayName text
)''');
User user = User.withIDs(1,'John Doe');
await db.insert(tblUsers, user.toMap());
//just to be slow
await Future.delayed(Duration(milliseconds: 2000), () {});
return db;
}
//data class for completeness...nothing amazing
class User {
int _userid;
String _displayName;
User(this._displayName);
User.withIDs(this._userid, this._displayName);
int get userid => _userid;
String get displayName => _displayName;
set displayName(String displayName) {
_displayName = displayName;
}
String toString() {
return
"userid: " + _userid.toString() + "n" +
"displayName: " + _displayName + "n";
}
User.fromMap(dynamic o) {
this._userid=o["userid"];
this._displayName=o["displayName"];
}
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (_userid != null) {
map["userid"] = _userid;
}
map["displayName"] = _displayName;
return map;
}
}
flutter conventions
flutter conventions
asked Nov 21 at 21:28
Minh
316
316
add a comment |
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53420702%2fflutter-async-convention-best-practices-for-state-and-widget%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown