Hi I am new to flutter and experimenting with statefull widget. I am trying to build a timer and want to show it in a Text.
This is my widget class.
class MobileVerification extends StatefulWidget {
static const String MOBILE = 'mobile';
final Map<String, dynamic> args;
MobileVerification(this.args);
#override
State<StatefulWidget> createState() {
return MobileVerificationState(args);
}
}
This is my State class
class MobileVerificationState extends State<MobileVerification> {
int remainingTime = 30;
Timer timer;
bool isResendBtnEnabled = false;
MobileVerificationState(this.args);
#override
void initState() {
super.initState();
startResendTimer();
}
startResendTimer() {
timer = new Timer.periodic(new Duration(seconds: 1), (time) {
setState(() {
remainingTime -= 1;
});
if (remainingTime == 0) {
time.cancel();
setState(() {
isResendBtnEnabled = true;
});
}
});
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CustomScreenBg(
body: _getOtpVerificationPage());
}
_getOtpVerificationPage() {
return Container(
margin: EdgeInsets.only(top: 24, bottom: 24, left: 12, right: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
getResendWidget(),
CustomButton(
AppString.verify,
isValidateBtnEnabled ? _onValidate : null,
textColor: AppColor.WHITE.color,
bgColor: AppColor.PRIMARY.color,
),
],
),
);
}
Widget getResendWidget() {
Logger.d(remainingTime.toString()); // Here I am getting changed value.
if (isResendBtnEnabled) {
return CustomButton(
AppString.resend,
_onResend,
textColor: AppColor.WHITE.color,
bgColor: AppColor.PRIMARY.color,
);
}
return RichText(
text: TextSpan(
text: 'Resend in ${remainingTime}s', <== But here value is not getting reflected. It stays same as 30
style: TextStyle(color: AppColor.GRAY.color, fontSize: 18),
),
);
}
}
Timer is working perfectly fine, and I am getting updated value as well. But the updated value is not getting reflected inside RichText. Can someone point me out, where i am making the mistake?
Thanks!!
I was making a mistake. I was using StatefulWidget instead of statelessWidget for inheritance. Hope it can help someone. I have created my own custom widget to use. Structure of my widget is follows:
class CustomScreenBg extends StatefulWidget {
final String title;
final Widget body;
CustomScreenBg(this.title, this.body);
#override
State<StatefulWidget> createState() {
return _CustomScreenBgState(title, body);
}
}
class _CustomScreenBgState extends State<CustomScreenBg> {
final String title;
final Widget body;
_CustomScreenBgState(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
backgroundColor: AppColor.SNOW_WHITE.color,
body: SingleChildScrollView(child: body),
resizeToAvoidBottomInset: true,
);
}
}
I changed it to following and it is working fine.
class CustomScreenBg extends StatelessWidget {
final String title;
final Widget body;
CustomScreenBg(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
backgroundColor: AppColor.SNOW_WHITE.color,
body: SingleChildScrollView(child: body),
resizeToAvoidBottomInset: true,
);
}
}
Related
I am trying to get a new historyTile() to be called to the Scaffold() each second. I am unsure how to make the void function connect.
Any advice and feedback is appreciated!
Code:
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
#override
void historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jm().format(now);
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
}
);
}
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (Timer t) => historyTile());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:
Container(
child: SingleChildScrollView(
child: historyTile(); // ERROR HERE
),
),
);
}
}
You can try creating periodic streams with a Stream Builder widget. If not, the simplest method is to put your widget in scaffold and try calling the setState function periodically with a 1-second timer.
In the StreamBuilder example you should change the widget a bit. Sending the parameter you want to update to the widget from outside will add a little more flexibility to you.
return Scaffold(
body: StreamBuilder<String>(
stream: Stream.periodic(const Duration(seconds: 1), (x) {
// Your Action Here
final now = DateTime.now();
return DateFormat.yMMMMd().add_jm().format(now);
}),
builder: (context, snapshot) {
String param = "";
if (snapshot.hasData) param = snapshot.data!;
return _historyTile(txt = param);
}
),
);
Or you could use your widget in Scaffold Body and periodically set the widgets state in timer callback.
class _activityTabState extends State<activityTab> {
String tileTime = "";
...
Timer.periodic(Duration(seconds: 1), () {
setState(() {
final now = DateTime.now();
tileTime = DateFormat.yMMMMd().add_jm().format(now);
});
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: historyTile(tileName);
),
),
);
}
or just
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('$tileTime'),
tileColor: Colors.greenAccent,
),
),
),
);
}
Create your historyTile widget as a custom widget
class HistoryTile extends StatefulWidget {
const HistoryTile({Key? key, required this.txt}) : super(key: key);
final String txt;
#override
State<HistoryTile> createState() => _HistoryTileState();
}
class _HistoryTileState extends State<HistoryTile> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text(widget.txt),
tileColor: Colors.greenAccent,
);
}
}
there is some issues in you ListView.Builder. You do not put itemCount there. And you need to use setState in timer. So codes are below. Please check.
class activityTab extends StatefulWidget {
const activityTab({Key? key}) : super(key: key);
#override
State<activityTab> createState() => _activityTabState();
}
class _activityTabState extends State<activityTab> {
String _now;
Timer _everySecond;
#override
historyTile() {
final now = DateTime.now();
String tileTime = DateFormat.yMMMMd().add_jms().format(now);
return ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.backup_outlined),
title: Text('Synced my_script.pdf with the cloud.'),
subtitle: Text('${tileTime}'),
tileColor: Colors.greenAccent,
);
});
}
void _timer() {
Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
_timer();
});
});
}
#override
void initState() {
super.initState();
_timer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: SingleChildScrollView(
child: historyTile(),
),
),
);
}
}
I'm new to flutter.I have here 3 classes which are the Login(), MainMenu() which is the screen after already logged, and this MyDrawer()which is a drawer of my App.
What I'm trying to do is I want to access the signOut() method from Login(). How would I do it or what should I do to redesign my code. I've tried below accessing it and it receives and exception The method 'call' was called on null.
This is a code snippet from my full code:
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
enum LoginStatus { notSignIn, signIn }
class _LoginState extends State<Login> {
LoginStatus _loginStatus = LoginStatus.notSignIn;
String email, password;
final _key = new GlobalKey<FormState>();
bool _secureText = true;
signOut() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
setState(() {
_loginStatus = LoginStatus.notSignIn;
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
switch (_loginStatus) {
case LoginStatus.notSignIn:
return Scaffold(
backgroundColor: Colors.cyan,
body: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.all(15.0),
children: <Widget>[
Center(
child: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.cyan,
child: Form(
key: _key,
break;
case LoginStatus.signIn:
return MainMenu();
break;
}
}
}
class MainMenu extends StatefulWidget {
#override
_MainMenuState createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
)
)
}
class MyDrawer extends StatefulWidget {
final Function onTap;
final VoidCallback signOut;
MyDrawer(
{this.onTap,this.signOut
});
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
signOut() {
setState(() {
widget.signOut();
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery
.of(context)
.size
.width * 0.7,
child: Drawer(
child: Container(
height:100,
color: Colors.white,
child: ListView(
padding: EdgeInsets.all(0),
children: <Widget>[
ListTile(
leading: Icon(Icons.exit_to_app,color: Colors.cyan, size: 30.0),
onTap: () {
signOut();
},
title: Text("Logout",
style: TextStyle(color: Colors.black,fontWeight: FontWeight.w500, fontSize: 18),
),
),
],
),
),
),
);
}
}
I'm really stuck with this problem. Any help would be greatly appreciated. Thanks!
What I want is when I click the switch button, the text in the Option1Content widget should change to true or false (depending upon the current value of the switch). The value is correct when you click the tile, select a different option from the drawer, and then come back to option 1, you will have the correct value. My requirement is when I press the switch tile the value of Option1Content should change instantly. For functionality reference: https://dartpad.dev/c9cabc35a0bda57758b1d1cf07f8a823. Any help would be greatly appreciated. Thank you.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget{
MyWidgetState createState()=> MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool status;
Widget myBody;
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void closeDrawer() {
if (_scaffoldKey.currentState.isDrawerOpen) {
_scaffoldKey.currentState.openEndDrawer();
}
}
#override
void initState(){
super.initState();
status = false;
myBody = Option1Content(status:status);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key:_scaffoldKey,
appBar:AppBar(
iconTheme: IconThemeData(color: Colors.black),
elevation:0,
backgroundColor:Colors.transparent,
actions:[
Switch(
inactiveThumbColor: Colors.black,
activeColor: Colors.green,
value:status,
onChanged:(value){
setState((){
status=value;
});
})
]
),
drawer: Drawer(
child:Center(child:ListView(children:[
DrawerHeader(
child: Column(
children: <Widget>[
CircleAvatar(
radius: 50,
backgroundColor: Colors.grey,
),
Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, top: 12.0),
child: Text(
'Account',
style: TextStyle(
fontWeight: FontWeight.bold,
),
textScaleFactor: 1.3,
),
),
],
),
),
ListTile(title:Center(child:Text('Option 1')),onTap:(){
closeDrawer();
setState((){
myBody = Option1Content(status:status);
});
}),
ListTile(title:Center(child:Text('Option 2')),onTap:(){
closeDrawer();
setState((){
myBody = Center(child:Text('Option 2 Content'));
});
}),
ListTile(title:Center(child:Text('Option 3')),onTap:(){
closeDrawer();
setState((){
myBody = Center(child:Text('Option 3 Content'));
});
}),
]))
),
body: myBody
);
}
}
class Option1Content extends StatefulWidget {
final bool status;
Option1Content({#required this.status});
#override
_Option1ContentState createState() => _Option1ContentState();
}
class _Option1ContentState extends State<Option1Content> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('${widget.status}'),
);
}
}
The issue is that simply changing the value of status doesn't update what is actually in myBody, which is what's shown. Even when changing status with setState, myBody still contains your widget with the old value of status. This is why when you go to another myBody and come back, it's updated, because myBody now has the new widget with the updated status value.
To solve this you need to have a method of updating what's contained in myBody, because that's the only part that's being built. Doing the following is the simplest change.
Just change
setState((){
status = value;
});
to
setState((){
status = value;
myBody = Option1Content(status:status);
});
and the full code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget{
MyWidgetState createState()=> MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool status;
Widget myBody;
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void closeDrawer() {
if (_scaffoldKey.currentState.isDrawerOpen) {
_scaffoldKey.currentState.openEndDrawer();
}
}
#override
void initState(){
super.initState();
status = false;
myBody = Option1Content(status:status);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key:_scaffoldKey,
appBar:AppBar(
iconTheme: IconThemeData(color: Colors.black),
elevation:0,
backgroundColor:Colors.transparent,
actions:[
Switch(
inactiveThumbColor: Colors.black,
activeColor: Colors.green,
value:status,
onChanged:(value){
setState((){
status = value;
myBody = Option1Content(status:status);
});
})
]
),
drawer: Drawer(
child:Center(child:ListView(children:[
DrawerHeader(
child: Column(
children: <Widget>[
CircleAvatar(
radius: 50,
backgroundColor: Colors.grey,
),
Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, top: 12.0),
child: Text(
'Account',
style: TextStyle(
fontWeight: FontWeight.bold,
),
textScaleFactor: 1.3,
),
),
],
),
),
ListTile(title:Center(child:Text('Option 1')),onTap:(){
closeDrawer();
setState((){
myBody = Option1Content(status:status);
});
}),
ListTile(title:Center(child:Text('Option 2')),onTap:(){
closeDrawer();
setState((){
myBody = Center(child:Text('Option 2 Content'));
});
}),
ListTile(title:Center(child:Text('Option 3')),onTap:(){
closeDrawer();
setState((){
myBody = Center(child:Text('Option 3 Content'));
});
}),
]))
),
body: myBody
);
}
}
class Option1Content extends StatefulWidget {
final bool status;
Option1Content({#required this.status});
#override
_Option1ContentState createState() => _Option1ContentState();
}
class _Option1ContentState extends State<Option1Content> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('${widget.status}'),
);
}
}
I'm doing a flutter app with Cupertino, I'm trying to figure out how to recall method initState each time that I navigate to this tab (MapPage).
This method initState() calls other mehtod "initPlatformState()" who asks for the permission location and makes a request to an API, with this result I build a marker per each object's result and show them on the map.
Here is the code of the map page.
https://gist.github.com/GreyHat147/3ea92f4e962218893b84af667452b087
This is the ui.
For CupertinoTabBar you can do as below where create instance of state class and the without creating it again called it only on tap:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
MyHome myHome = new MyHome();
MyNearMe myNearMe = new MyNearMe();
MyMap myMap = new MyMap();
MyNotifications myNotifications = new MyNotifications();
MyWallet myWallet = new MyWallet();
MyHomeState myHomeState = MyHomeState();
MyNearMeState myNearMeState = MyNearMeState();
MyMapState myMapState = MyMapState();
MyNotificationsState myNotificationsState = MyNotificationsState();
MyWalletState myWalletState = MyWalletState();
int indexPrevValue = 0;
class TabBarPage extends StatefulWidget {
TabBarPage({Key key, this.userId})
: super(key: key);
final String userId;
#override
_TabBarPage createState() => new _TabBarPage();
}
class _TabBarPage extends State<TabBarPage> {
_TabBarPage({Key key, this.userId});
final String userId;
void _onTap(int value) {
print('Value => $value');
if(value == 0){
myHomeState.initState();
}
else if(value == 1){
myNearMeState.initState();
}
else if(value == 2){
myMapState.initState();
}
else if(value == 3){
myNotificationsState.initState();
}
else if(value == 4){
myWalletState.initState();
}
indexPrevValue = value;
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
onTap: _onTap,
activeColor: new Color.fromRGBO(148, 3, 123, 1.0),
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Home'),
icon: new Icon(
Icons.home,
size: 22,
),
),
new BottomNavigationBarItem(
title: new Text('Near me'),
icon: new Icon(
Icons.location_on,
size: 22,
),
),
new BottomNavigationBarItem(
icon: new Icon(
Icons.map,
size: 22,
),
title: new Text('Map')
),
new BottomNavigationBarItem(
title: new Text('Notifications'),
icon: new Icon(
Icons.notifications,
size: 22,
)
),
new BottomNavigationBarItem(
title: new Text('Wallet'),
icon: new Icon(
Icons.account_balance_wallet,
size: 22,
)
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
builder: (BuildContext context) {
if(myHomeState == null){
myHomeState = myHome.createState();
}
return myHome.createState().build(context);
},
);
break;
case 1:
return CupertinoTabView(
builder: (BuildContext context) {
if(myNearMeState == null){
myNearMeState = myNearMe.createState();
}
return myNearMe.createState().build(context);
},
);
break;
case 2:
return CupertinoTabView(
builder: (BuildContext context) {
if(myMapState == null){
myMapState = myMap.createState();
}
return myMap.createState().build(context);
},
);
break;
case 3:
return CupertinoTabView(
builder: (BuildContext context) {
if(myNotificationsState == null){
myNotificationsState = myNotifications.createState();
}
return myNotifications.createState().build(context);
},
);
break;
case 4:
return CupertinoTabView(
builder: (BuildContext context) {
if(myWalletState == null){
myWalletState = myWallet.createState();
}
return myWallet.createState().build(context);
},
);
break;
}
},
);
}
}
class MyHome extends StatefulWidget {
#override
MyHomeState createState() => new MyHomeState();
}
class MyHomeState extends State<MyHome> {
#override
void initState() {
super.initState();
print('MyHomeState initState() called');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("test stream"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('HOME 1')
],
),
));
}
}
class MyNearMe extends StatefulWidget {
#override
MyNearMeState createState() => new MyNearMeState();
}
class MyNearMeState extends State<MyNearMe> {
#override
void initState() {
super.initState();
print('MyNearMeState initState() called');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("MyNearMe"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('My Near Me')
],
),
));
}
}
class MyMap extends StatefulWidget {
#override
MyMapState createState() => new MyMapState();
}
class MyMapState extends State<MyMap> {
#override
void initState() {
super.initState();
print('MyMapState initState() called');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("MyMap"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('My Map')
],
),
));
}
}
class MyNotifications extends StatefulWidget {
#override
MyNotificationsState createState() => new MyNotificationsState();
}
class MyNotificationsState extends State<MyNotifications> {
#override
void initState() {
super.initState();
print('MyNotificationsState initState() called');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("MyNotifications"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('My Notifications')
],
),
));
}
}
class MyWallet extends StatefulWidget {
#override
MyWalletState createState() => new MyWalletState();
}
class MyWalletState extends State<MyWallet> {
#override
void initState() {
super.initState();
print('MyWalletState initState() called');
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("MyWallet"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('My Wallet')
],
),
));
}
}
If you want to use another bottom navigation bar instead of cupertino then you can use as below:
I have created a sample example for you where initState() will call every time on tab change whether its same tab or different tab:
First of all import "bmnav: ^0.3.4" library in pubspec.yaml and then copy and paste below code:
import 'package:flutter/material.dart';
import 'package:bmnav/bmnav.dart' as bmnav;
Widget currentScreen = null;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
currentScreen = MyHomeMapSample();
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();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
}
int currentTab = 0;
int prevTab = 0;
final PageStorageBucket bucket = PageStorageBucket();
#override
Widget build(BuildContext ctx) {
debugPrint('currentTab: $currentTab');
return Scaffold(
body: PageStorage(child: currentScreen, bucket: bucket),
bottomNavigationBar: SizedBox(height: 58,
child: bmnav.BottomNav(
index: currentTab,
onTap: (i) {
setState(() {
currentTab = i;
currentScreen = getWidget(context, i);
if(prevTab==currentTab){
if(i==0){
MyHomeMapSample map = currentScreen as MyHomeMapSample;
map.createState().initState();
}else if(i==1){
MyHomeWorkouts map = currentScreen as MyHomeWorkouts;
map.createState().initState();
}
else if(i==2){
MyHomeAccount map = currentScreen as MyHomeAccount;
map.createState().initState();
}
}
prevTab = currentTab;
});
},
labelStyle: bmnav.LabelStyle(visible: true),
items: [
bmnav.BottomNavItem(Icons.map, label: 'Map'),
bmnav.BottomNavItem(Icons.cast, label: 'Workouts'),
bmnav.BottomNavItem(Icons.textsms, label: 'Account'),
],
),
),
resizeToAvoidBottomPadding: true,
);
}
Widget getWidget(BuildContext context, int i){
if(i==0){
return MyHomeMapSample();
}
else if(i==1){
return MyHomeWorkouts();
}else if(i==2){
return MyHomeAccount();
}
}
}
class MyHomeMapSample extends StatefulWidget {
MyHomeMapSample({Key key}) : super(key: key);
#override
MapSample createState() => MapSample();
}
class MapSample extends State<MyHomeMapSample> {
var myVariable = 0;
#override
void initState(){
super.initState();
debugPrint('current: MapSample: initState() called!');
}
#override
Widget build(BuildContext context) {
myVariable = myVariable + 1;
return Scaffold(
appBar: AppBar(
title: Text('MapSample'),
),
body: Center(
child: Text('MapSample details + $myVariable'),
),
resizeToAvoidBottomPadding: true,
);
}
}
class MyHomeWorkouts extends StatefulWidget {
MyHomeWorkouts({Key key}) : super(key: key);
#override
Workouts createState() => Workouts();
}
class Workouts extends State<MyHomeWorkouts> {
var myVariable = 0;
#override
void initState(){
super.initState();
debugPrint('current: Workouts: initState() called!');
}
#override
Widget build(BuildContext context) {
myVariable = myVariable + 1;
return Scaffold(
appBar: AppBar(
title: Text('Workouts'),
),
body: Center(
child: Text('Workouts details + $myVariable'),
),
resizeToAvoidBottomPadding: true,
);
}
}
class MyHomeAccount extends StatefulWidget {
MyHomeAccount({Key key}) : super(key: key);
#override
Account createState() => Account();
}
class Account extends State<MyHomeAccount> {
var myVariable = 0;
#override
void initState(){
super.initState();
debugPrint('current: Account: initState() called!');
}
#override
Widget build(BuildContext context) {
myVariable = myVariable + 1;
return Scaffold(
appBar: AppBar(
title: Text('Account'),
),
body: Center(
child: Text('Account details + $myVariable'),
),
resizeToAvoidBottomPadding: true,
);
}
}
I have two tabs, the left tab having a list of tiles and the right tab having nothing. The user can drag the screen from right-to-left or left-to-right to get from one tab to the other.
The left tab has a list of dismissible tiles that only have "direction: DismissDirection.startToEnd" (from left-to-right) enabled so that the user can still theoretically drag (from right-to-left) to go to the right tab.
However, I believe the Dismissible widget still receives the right-to-left drag information which is disabling the TabView drag to change tabs.
In essence, how do I allow the right-to-left drag to be detected by only the TabView and not the Dismissible item?
If an explicit solution/example with code snippets can be given, I would very very much appreciate the help!
Here's a paste for your main.dart file:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/semantics.dart';
void main() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(vsync: this, length: 2, initialIndex: 1);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Colors.black,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TabBarView(
controller: _tabController,
children: <Widget>[
TabWithSomething(),
TabWithNothing(),
],
),
),
],
),
),
),
);
}
}
class TabWithNothing extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Text("Swipe from left-to-right!"),
),
);
}
}
class TabWithSomethingItem implements Comparable<TabWithSomethingItem> {
TabWithSomethingItem({this.index, this.name, this.subject, this.body});
TabWithSomethingItem.from(TabWithSomethingItem item)
: index = item.index,
name = item.name,
subject = item.subject,
body = item.body;
final int index;
final String name;
final String subject;
final String body;
#override
int compareTo(TabWithSomethingItem other) => index.compareTo(other.index);
}
class TabWithSomething extends StatefulWidget {
const TabWithSomething({Key key}) : super(key: key);
static const String routeName = '/material/leave-behind';
#override
TabWithSomethingState createState() => TabWithSomethingState();
}
class TabWithSomethingState extends State<TabWithSomething> {
List<TabWithSomethingItem> TabWithSomethingItems;
void initListItems() {
TabWithSomethingItems =
List<TabWithSomethingItem>.generate(10, (int index) {
return TabWithSomethingItem(
index: index,
name: 'Item $index',
subject: 'Swipe from left-to-right to delete',
body: "Swipe from right-to-left to go back to old tab");
});
}
#override
void initState() {
super.initState();
initListItems();
}
void _handleDelete(TabWithSomethingItem item) {
setState(() {
TabWithSomethingItems.remove(item);
});
}
#override
Widget build(BuildContext context) {
Widget body;
body = ListView(
children:
TabWithSomethingItems.map<Widget>((TabWithSomethingItem item) {
return _TabWithSomethingListItem(
item: item,
onDelete: _handleDelete,
dismissDirection: DismissDirection.startToEnd,
);
}).toList());
return body;
}
}
class _TabWithSomethingListItem extends StatelessWidget {
const _TabWithSomethingListItem({
Key key,
#required this.item,
#required this.onDelete,
#required this.dismissDirection,
}) : super(key: key);
final TabWithSomethingItem item;
final DismissDirection dismissDirection;
final void Function(TabWithSomethingItem) onDelete;
void _handleDelete() {
onDelete(item);
}
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Semantics(
customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
const CustomSemanticsAction(label: 'Delete'): _handleDelete,
},
child: Dismissible(
key: ObjectKey(item),
direction: dismissDirection,
onDismissed: (DismissDirection direction) => _handleDelete(),
background: Container(
color: theme.primaryColor,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0))),
child: Container(
decoration: BoxDecoration(
color: theme.canvasColor,
border: Border(bottom: BorderSide(color: theme.dividerColor))),
child: ListTile(
title: Text(item.name),
subtitle: Text('${item.subject}\n${item.body}'),
isThreeLine: true),
),
),
);
}
}
UPDATE:
I'm thinking we could change the "dismissible.dart" file to change the "TabControlller", but i'm not sure how I might do that.
In the "dismissible.dart" file:
...
void _handleDragUpdate(DragUpdateDetails details) {
if (!_isActive || _moveController.isAnimating)
return;
final double delta = details.primaryDelta;
if (delta < 0) print(delta); // thinking of doing something here
...