I'm trying to create a form with tabs. In each tab I have TextFormField. Tabs have AutomaticKeepAliveClientMixin to keep their State. Tabs can be created dynamically. When new tab is created, it is inserted in the middle of the tabs list.
Problem: when new tab is inserted, its TextFormField keep state of the next tab and so on. It seems states keeps in order from 1 to n. Is there any way to keep the right state for tabs?
Thanks in advance.
import 'package:flutter/material.dart';
class TabTesting extends StatefulWidget {
#override
_TabTestingState createState() => _TabTestingState();
}
class _TabTestingState extends State<TabTesting> with TickerProviderStateMixin {
List<MyTab> _tabs = [
MyTab(TabData("1", "1")),
MyTab(TabData("3", "3")),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: _tabs.length);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TabTesting"),
bottom: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.symmetric(vertical: 16.0),
tabs: _tabs.map((tab) => Text(tab.tabData.name)).toList(),
),
),
body: Container(
padding: EdgeInsets.all(32.0),
child: TabBarView(
controller: _tabController,
children: _tabs,
),
),
persistentFooterButtons: <Widget>[
RaisedButton(
child: Text("Add tab"),
onPressed: () {
final newTab = MyTab(TabData("2", "2"));
final newTabs = _tabs;
newTabs.insert(1, newTab);
final index = _tabController.index;
setState(() {
_tabs = newTabs;
_tabController = TabController(
vsync: this,
length: newTabs.length,
initialIndex: index);
});
},
),
],
);
}
}
class MyTab extends StatefulWidget {
final tabData;
MyTab(this.tabData);
#override
_MyTabState createState() => _MyTabState();
}
class _MyTabState extends State<MyTab> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
return Tab(
child: TextFormField(
decoration: InputDecoration(labelText: widget.tabData.name),
initialValue: widget.tabData.data,
),
);
}
}
class TabData {
String name;
String data;
TabData(this.name, this.data);
}
You forgot to add super.build(context); as the documentation of AutomaticKeepAliveClientMixin suggests.
#override
Widget build(BuildContext context) {
super.build(context);
return Tab(
child: TextFormField(
decoration: InputDecoration(labelText: widget.tabData.name),
initialValue: widget.tabData.data,
),
);
}
Related
I am using an HtmlElementView in my flutter web project for showing a web image/pdf etc.
I am not able to figure out a way to align the element within the frame. As shown by the below sample code example, if the image size is smaller than parent widget size, it is shown in top left alignment. I have a feeling this has to do with some iframe style setting but not able to figure out.
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
theme: ThemeData.dark(),
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: InteractiveViewer(
child: Container(
alignment: Alignment.center,
width: 3000,
height: 3000,
child: EmbedWebView(
src:
'https://picsum.photos/id/1/200/300', //'https://www.youtube.com/embed/3fB1mxOsqJE',
width: 500,
height: 550))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
);
}
}
class EmbedWebView extends StatefulWidget {
final Key? key;
final String src;
final double height, width;
EmbedWebView(
{this.key, required this.src, required this.height, required this.width});
#override
State<StatefulWidget> createState() {
return EmbedWebViewState();
}
}
class EmbedWebViewState extends State<EmbedWebView>
with AutomaticKeepAliveClientMixin {
#override
void initState() {
super.initState();
final IFrameElement _iframeElement = IFrameElement()
..height = '100%'
..width = '100%'
..src = widget.src
..style.border = 'none'
..style.overflow = "hidden"
..allow = "autoplay"
..allowFullscreen = true;
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
widget.src,
(int viewId) => _iframeElement,
);
}
#override
void dispose() {
super.dispose();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: Container(
color: Colors.white,
width: widget.width,
height: widget.height,
child: HtmlElementView(
// key: UniqueKey(),
viewType: widget.src,
),
),
);
}
}
When the above code is run using flutter run -d chrome, this is what I get. It can be seen that the image in not centred.
sample code output
Try wrapping the EmvedWebView with an Expanded. Probably the EmvedWebView is not occupying the whole Container, implying that even if the Container is centered his child is not
iam new to flutter but really i dont see what is the error in my code it first it keeps showing that :
The following assertion was thrown building Builder:
setState() or markNeedsBuild() called during build.
and when i hot relod this comes up
line 4016 pos 12: '!_debugLocked':
is not true.
i dont know what to do and why this is happening because its the first time
class _RepresentativeUiState extends State<RepresentativeUi> {
int _currentIndex = 0;
// navy bottom pages
final _newsFeedPage = RNewsFeed();
final _profilePage = RProfile();
final _addPostPage = RAddPost();
final _libraryPage = RLibrary();
Widget _showPage = RNewsFeed();
Widget _pageChooser(int page) {
switch (page) {
case 0:
return _newsFeedPage;
break;
case 1:
return _addPostPage;
break;
case 2:
return _libraryPage;
break;
case 3:
return _profilePage;
break;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
index: _currentIndex,
items: <Widget>[
Icon(
FontAwesomeIcons.newspaper,
size: 30,
),
Icon(
FontAwesomeIcons.plus,
size: 30,
),
Icon(
FontAwesomeIcons.book,
size: 30,
),
Icon(
FontAwesomeIcons.user,
size: 30,
),
],
color: Colors.white,
buttonBackgroundColor: Colors.white,
backgroundColor: Color(0xff131535),
animationCurve: Curves.easeIn,
animationDuration: Duration(milliseconds: 400),
onTap: (int tappedIndex) {
setState(() {
_showPage = _pageChooser(tappedIndex);
});
},
),
body: Container(
child: _showPage,
),
);
}
}
class RNewsFeed extends StatefulWidget {
#override
_RNewsFeedState createState() => _RNewsFeedState();
}
class _RNewsFeedState extends State<RNewsFeed> {
#override
Widget build(BuildContext context) {
return Container(
child: Text('1'),
);
}
}
class RAddPost extends StatefulWidget {
#override
_RAddPostState createState() => _RAddPostState();
}
class _RAddPostState extends State<RAddPost> {
#override
Widget build(BuildContext context) {
return Container(
child: Text('2'),
);
}
}
class RLibrary extends StatefulWidget {
#override
_RLibraryState createState() => _RLibraryState();
}
class _RLibraryState extends State<RLibrary> {
#override
Widget build(BuildContext context) {
return Container(
child: Text('3'),
);
}
}
class RProfile extends StatefulWidget {
#override
_RProfileState createState() => _RProfileState();
}
class _RProfileState extends State<RProfile> {
String _name ;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((value) => {
setState((){
value.getString('name');
})
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff131535),
body: SingleChildScrollView(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 50,
),
Text(
'$_name',
style: TextStyle(color: Colors.black),
),
],
),
),
),
);
}
}
This is the culprit i believe:
SharedPreferences.getInstance().then((value) => {
setState((){
value.getString('name');
})
});
There's no need to ever call setState() in initState(). So what happens now is that while the page is building, setState() is trying to initiate another build process. Simply remote the setState wrapper around value.getString('name');.
I want to make a widget(a fan) spin around untill the user clicks it.
And for that, I thought I was gonna use a function, but then the question is;
How do I make a looping function, that stoppes when a bool = false?
This is my current function (that doesn't loop):
#override
void initState() {
super.initState();
angleController = AnimationController(vsync: this, duration: Duration(milliseconds: 230));
angleController .addListener(() {
setState(() {
fanAngle = angleController.value * 90 / 360 * pi * 2;
});
});
}
And the GestureDetector function:
void fanRotation() {
if(angleController.status == AnimationStatus.completed){
angleController.stop();
fanState = false;
} else if (angleController.status == AnimationStatus.dismissed) {
angleController.forward();
fanState = true;
}
}
}
I want it to have a continuous loop, so no stopping and starting again.
You can copy paste run full code below
You can directly call angleController.repeat() in initState() and call angleController.stop() when click fan
code snippet
#override
void initState() {
super.initState();
angleController =
AnimationController(vsync: this, duration: Duration(seconds: 3));
angleController.repeat();
}
...
GestureDetector(
onTap: () {
angleController.stop();
},
child: Icon(
Icons.forward,
color: Colors.pink,
size: 100.0,
)),
working demo
full code
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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>
with SingleTickerProviderStateMixin {
AnimationController angleController;
#override
void initState() {
super.initState();
angleController =
AnimationController(vsync: this, duration: Duration(seconds: 3));
angleController.repeat();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: angleController,
builder: (_, child) {
return Transform.rotate(
angle: angleController.value * pi * 2,
child: child,
);
},
child: GestureDetector(
onTap: () {
angleController.stop();
},
child: Icon(
Icons.forward,
color: Colors.pink,
size: 100.0,
)),
),
],
),
),
);
}
}
You need a timer.periodic:
https://api.flutter.dev/flutter/dart-async/Timer/Timer.periodic.html
Use it to keep the rotation function running and just turn it off when clicked.
var timer = Timer.periodic(Duration(seconds: 5), (timer) {
//Your function goes here
});
// Cancel the timer
timer.cancel();
I have this a stateful widget which could be in two situations, the first one is a Container with a first text in it, for example, "Register", the second one is a different colored container with a different text, for example "confirm". The Problem is that the transition between these two situations is done using an animation and it's not an on-the-fly logic for example:
color: isSituation1 ? Colors.blue : Colors.red.
it's actually something like this:
color: Color.lerp(Colors.blue, Colors.red, _animation1.value)
and I have a function which runs when the user taps on the container which forwards the animation controller, like so:
_controller1.forward()
and this is a widget called let's say Button1
So in My HomePage stateful widget I have another button which should trigger the inverse process in the Button1 widget, so it would be:
_controller1.reverse()
I tried creating a function in the Button1 widget but then I cannot run it from outside. How could I do it if it's possible?
So basically you want to call methods of your CustomWidget from another widget. You can define ControllerClass that you will emit an instance when you create a new instance of your CustomWidget. This ControllerClass instance will hold the functions of your CustomWidget and you will be able to call them from outside.
By example a class that is a modal rounded progressbar that can be showed and hided from outise with a controller class. In this example a controller class is called ProgressBarHandler. I don't know if it is a better and the right approach but works.
class ModalRoundedProgressBar extends StatefulWidget {
final String _textMessage;
final double _opacity;
final Color _color;
final Function _handlerCallback;
ModalRoundedProgressBar({
#required Function handleCallback(ProgressBarHandler handler), //callback to get a controller
String message = "",
double opacity = 0.7,
Color color = Colors.black54,
}) : _textMessage = message,
_opacity = opacity,
_color = color,
_handlerCallback = handleCallback;
#override
State createState() => _ModalRoundedProgressBarState();
}
class _ModalRoundedProgressBarState extends State<ModalRoundedProgressBar> {
bool _isShowing = false;
#override
void initState() {
super.initState();
// init controller.
ProgressBarHandler handler = ProgressBarHandler();
handler.show = this.show;
handler.dismiss = this.dismiss;
widget._handlerCallback(handler); // callback call.
}
#override
Widget build(BuildContext context) {
if (!_isShowing) return Stack();
return Material(
color: Colors.transparent,
child: Stack(
children: <Widget>[
Opacity(
opacity: widget._opacity,
child: ModalBarrier(
dismissible: false,
color: Colors.black54,
),
),
Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Text(widget._textMessage),
],
),
),
],
),
);
}
void show() {
setState(() => _isShowing = true);
}
void dismiss() {
setState(() => _isShowing = false);
}
}
class ProgressBarHandler {
Function show; // will point to widget show method
Function dismiss; // will point to another method.
}
// ...in another external widget you can do...
// ... code your things and:
var controller;
var progressBar = ModalRoundedProgressBar(
handleCallback: ((handler){ controller = handler; } ),);
//calling show method with controller
RaisedButton(
onPressed: () { controller.show(); }
);
//calling dismiss method with controller
RaisedButton(
onPressed: () { controller.dismiss(); }
);
It is possible, but you probably don't want to do that. You should either provide the AnimationController from the parent or structure your app to prevent such behaviors. Anyway, if you still want to go this way, here you have it.
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
VoidCallback _reverseAnimation;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Reverse animation'),
onPressed: () => _reverseAnimation(),
),
Button1((controller) => _reverseAnimation = controller),
],
),
);
}
}
class Button1 extends StatefulWidget {
final ValueChanged<VoidCallback> callback;
Button1(this.callback);
_Button1State createState() => _Button1State();
}
class _Button1State extends State<Button1> with SingleTickerProviderStateMixin {
AnimationController _someAnimationController;
void _reverseAnimation() {
_someAnimationController?.reverse();
}
#override
void initState() {
super.initState();
if (widget.callback != null) {
widget.callback(_reverseAnimation);
}
}
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text('Start animation'),
onPressed: () => _someAnimationController.forward(),
),
);
}
}
I have the following code for the TabBar page:
class HomePage extends StatefulWidget {
static String tag = 'home-page';
#override
_homepage createState() => new _homepage();
}
class _homepage extends State<HomePage> with TickerProviderStateMixin{
AnimationController percentageAnimationController;
TabController _tabController;
#override
void initState() {
_tabController = new TabController(length: 3, vsync: this);
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
bottomNavigationBar: new Material(
color: Colors.white,
child: new TabBar(
controller: _tabController,
indicatorColor: Theme.Colors.loginGradientStart,
labelColor: Theme.Colors.loginGradientStart,
tabs: <Widget>[
new Tab(
icon: new Icon(wind_icon),
),
new Tab(
icon: new Icon(chart_icon),
),
new Tab(
icon: new Icon(settings_icon),
),
]
),
),
body:
new TabBarView(
children: <Widget>[
TabOne(),
TabTwo(),
TabThree(),
],
controller: _tabController,
),
);
}
}
Now if I navigate to this page it automatically opens the first tab but I want to open the second tab instead of the first i.e. tab index 1.
I am able to find out that we can achieve this by using _tabController.animateTo(1);
but I want to know how can I do this from button press of other pages.
You can Use Initial Index:
_tabController = new TabController(length: 3, vsync: this, initialIndex: 1);
I had similar problem. I created a file named "globals.dart".
my_app/globals.dart
library my_app.globals;
import 'package:flutter/material.dart';
TabController tabController;
And, after you set the tab controller, copy reference to this.
_homepage Class
import 'package:my_app/globals.dart' as globals;
#override
void initState() {
_tabController = new TabController(length: 3, vsync: this);
globals.tabController = _tabController;
super.initState();
}
other_page Class
import 'package:my_app/globals.dart' as globals;
...(in any function you want)...
globals.tabController.animateTo(2);
At this moment, if you don't need to expose the controller for other reasons and are using DefaultTabController, you can use the class initialIndex property :
DefaultTabController(
length: 3,
initialIndex: 1,
child: ...
You can check a DartPad demo here