dart constructor return null - flutter

This is MainPage class I pass parameters from one class to MainPage class. Before passing the argument I print the list. List values are not null. But after pass the parameter to MainPage returns null.
import 'package:flutter/material.dart';
import 'package:rummy/gameLogic/mainpage.dart';
class Cards extends StatefulWidget {
const Cards({Key? key, this.cardStatus}) : super(key: key);
final bool? cardStatus;
#override
State<Cards> createState() => _CardsState();
}
class _CardsState extends State<Cards> {
List<String>? userPickedList = [];
List<String> list = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
];
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height / 6,
width: MediaQuery.of(context).size.width / 1.0001,
color: Colors.transparent,
child: ReorderableListView(
scrollDirection: Axis.horizontal,
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex--;
}
final String item = list.removeAt(oldIndex);
list.insert(newIndex, item);
});
},
children: <Widget>[
for (int index = 0; index < list.length; index++)
GestureDetector(
key: ValueKey(index),
onTap: () {
setState(() {
userPickedList!.add(list[index]);
widget.cardStatus! ? list.removeAt(index) : null;
Mainpage(
// tappedStatus: tapped,
userPick: userPickedList?.last,
);
print(userPickedList?.last);
});
},
child: Card(
child: Container(
height: MediaQuery.of(context).size.height / 4,
width: MediaQuery.of(context).size.width / 16.7,
alignment: Alignment.bottomCenter,
child: Text(list[index]),
),
),
)
],
));
}
}
import 'package:rummy/gameLogic/cards.dart';
class Mainpage extends StatefulWidget {
const Mainpage(
{Key? key, this.tappedStatus,this.userPick,
this.shuffledCard})
: super(key: key);
final bool? tappedStatus;
final String? shuffledCard;
final String? userPick;
#override
_MainpageState createState() => _MainpageState();
}
class _MainpageState extends State<Mainpage> {
#override
void initState() {
super.initState();
}
bool cardDrop = false;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
bottom: 0,
child: Container(
color: Colors.white, child: Cards(cardStatus: cardDrop)),
),
Positioned(
top: MediaQuery.of(context).size.height / 3.5,
left: MediaQuery.of(context).size.width / 2.8,
child: Container(
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
height: MediaQuery.of(context).size.height / 4,
width: MediaQuery.of(context).size.height / 6,
color: Colors.black,
child: const Center(
child: Text(
'Shuffled card',
style: TextStyle(color: Colors.white),
),
),
)),
Positioned(
top: MediaQuery.of(context).size.height / 3.5,
left: MediaQuery.of(context).size.width / 1.8,
child: Container(
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
height: MediaQuery.of(context).size.height / 4,
width: MediaQuery.of(context).size.height / 6,
color: Colors.black,
child: Center(
child: Text(
widget.userPick.toString(),
style: const TextStyle(color: Colors.white),
),
),
)),
Positioned(
top: MediaQuery.of(context).size.height / 2,
right: MediaQuery.of(context).size.width / 38,
child: GestureDetector(
onTap: () {
setState(() {
cardDrop = true;
Cards(cardStatus: cardDrop);
if (widget.tappedStatus == true) {
cardDrop = false;
Cards(cardStatus: cardDrop);
}
// Future.delayed(const Duration(seconds: 10));
});
},
child: Container(
height: MediaQuery.of(context).size.height / 6,
width: MediaQuery.of(context).size.width / 8,
color: Colors.black,
child: const Center(
child: Text(
'Drop Card',
style: TextStyle(color: Colors.white),
)),
),
))
],
);
}
}
This is my full code.
I have two class mainpage and cards. Both are passing arguments with each other.

try to replace the userPickedList!.last
with userPickedList?.last

If you make your MainPage visible (for example give it a pink background) you will see that your problem is not with your parameter being null, your problem is that it is never displayed at all.
It's not in the tree of the build method.
It is hard to know what you wanted to achieve. You need to put your MainPage into the tree of visible elements you return from your build function. Your Cards class has the same problems. It's nto in the tree you return from your build method. It will never be visible.
In addition, I encourage you to read a tutorial for null-safety, you are making your life a lot harder than it should be. Once you understand it, there will be a lot less guessing and a lot less ! to place around your code. The better you get at it, the more helpful your compiler messages will become.

Related

Flutter - How to flip a single card from swipe stack?

I am using swipe_stack
I need to flip the front card when I click on it. The problem currently I am facing is that the entire stacks get flipped.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:quiero_funku/widgets/appbar.dart';
import '../../utils/swipe_stack.dart';
class SwipeDeck extends StatefulWidget {
const SwipeDeck({Key? key}) : super(key: key);
#override
State<SwipeDeck> createState() => _SwipeDeckState();
}
class _SwipeDeckState extends State<SwipeDeck>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
AnimationStatus _status = AnimationStatus.dismissed;
List<AnimationController> dataCtrl = <AnimationController>[];
// initialize _controller, _animation
#override
void initState() {
super.initState();
// add some AnimationController object before using any index
dataCtrl.add(AnimationController(vsync: this, duration: const Duration(seconds: 1)));
for (int i = 1; i < 10; i++) {
var "_controller$i" = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
"_animation$i" = Tween(end: 1.0, begin: 0.0).animate(_controller)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
_status = status;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppbar(
onBackPressed: () {},
title: '',
),
body: Container(
padding: const EdgeInsets.all(20),
height: 300,
width: double.infinity,
child: SwipeStack(
children: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((int index) {
return SwiperItem(
builder: (SwiperPosition position, double progress) {
return Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.0015)
..rotateX(pi * _animation.value),
child: GestureDetector(
onTap: () {
print("tapped");
if (_status == AnimationStatus.dismissed) {
_controller.forward();
} else {
_controller.reverse();
}
},
child: Material(
elevation: 4,
borderRadius: const BorderRadius.all(Radius.circular(6)),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(6)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"Item $index",
style: const TextStyle(
color: Colors.black,
fontSize: 20,
),
),
Text(
"Progress $progress",
style: const TextStyle(
color: Colors.blue,
fontSize: 12,
),
),
],
),
),
),
),
);
});
}).toList(),
visibleCount: 3,
stackFrom: StackFrom.Right,
translationInterval: 6,
scaleInterval: 0.03,
onEnd: () => debugPrint("onEnd"),
onSwipe: (int index, SwiperPosition position) =>
debugPrint("onSwipe $index $position"),
onRewind: (int index, SwiperPosition position) =>
debugPrint("onRewind $index $position"),
),
),
);
}

Saving state of a widget in Stepper flutter

I created a stepper (4 steps) with two buttons for next and previous. Each step has a form, and each form is in a widget in its own class.
The first problem is that every time I click the previous button, the data in the text fields disappear.
How can I preserve the state of each widget in each step?
The second problem, I want the last step to be a summary of what the user has entered in the previous steps. What is the best way to get the data from each step and display them in the last step?
I would really appreciate it if you could give me a solution. Thank you
I tried using AutomaticKeepAliveClientMixin but it didn't work .
import 'package:flutter/material.dart';
class CustomeStepper extends StatelessWidget {
final double width;
final List<IconData> icons;
final List<String> titles;
final int curStep;
final Color circleActiveColor;
final Color circleInactiveColor;
final Color iconActiveColor;
final Color iconInactiveColor;
final Color textActiveColor;
final Color textInactiveColor;
final double lineWidth = 4.0;
final List<Widget> content;
CustomeStepper(
{required this.icons,
required this.curStep,
required this.titles,
required this.width,
required this.circleActiveColor,
required this.circleInactiveColor,
required this.iconActiveColor,
required this.iconInactiveColor,
required this.textActiveColor,
required this.textInactiveColor,
required this.content})
: assert(curStep > 0 && curStep <= icons.length),
assert(width > 0);
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: width,
padding: const EdgeInsets.only(
top: 32.0,
left: 24.0,
right: 24.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: _iconViews(),
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _titleViews(),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 16),
child: content[curStep - 1]),
)
],
)),
);
}
List<Widget> _iconViews() {
var list = <Widget>[];
icons.asMap().forEach((i, icon) {
var circleColor = (i == 0 || curStep >= i + 1)
? circleActiveColor
: circleInactiveColor;
var lineColor = (i == 0 || curStep >= i + 1)
? circleActiveColor
: circleInactiveColor;
var iconColor =
(i == 0 || curStep >= i + 1) ? iconActiveColor : iconInactiveColor;
list.add(
Container(
width: 50.0,
height: 50.0,
padding: const EdgeInsets.all(0),
child: Icon(
icon,
color: iconColor,
size: 25.0,
),
decoration: BoxDecoration(
color: circleColor,
borderRadius: const BorderRadius.all(
Radius.circular(25.0),
),
),
),
);
//line between icons
if (i != icons.length - 1) {
list.add(Expanded(
child: Container(
height: lineWidth,
color: lineColor,
)));
}
});
return list;
}
List<Widget> _titleViews() {
var list = <Widget>[];
titles.asMap().forEach((i, text) {
var _textColor =
(i == 0 || curStep > i + 1) ? textActiveColor : textInactiveColor;
list.add(
Container(
width: 50.0,
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(0),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(color: _textColor, fontWeight: FontWeight.bold),
),
),
);
});
return list;
}
}
import 'package:flutter/material.dart';
import 'package:project_five/widgets/business/adding_product_widgets/first_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/four_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/second_step.dart';
import 'package:project_five/widgets/business/adding_product_widgets/third_step.dart';
import 'package:project_five/widgets/business/custome_stepper.dart';
class AddProduct extends StatefulWidget {
const AddProduct({Key? key}) : super(key: key);
#override
State<AddProduct> createState() => _AddProductState();
}
class _AddProductState extends State<AddProduct> {
static const _stepIcons = [
Icons.add_circle,
Icons.document_scanner,
Icons.camera_alt_rounded,
Icons.check,
];
static const _titles = ['المنتج', 'تفاصيل', 'الصور', 'نشر'];
var _contnet = [
FirstStep(),
SecondStep(),
ThirdStep(),
Forth()];
var _curStep = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('إضافة منتج'),
centerTitle: true,
),
persistentFooterButtons: [
Row(
children: [
Expanded(
child: ElevatedButton(
child: const Text('التالي'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
onPressed: () => setState(() {
if (_curStep < _stepIcons.length) _curStep++;
}),
),
),
const SizedBox(
width: 8,
),
Expanded(
child: ElevatedButton(
child: const Text('رجوع'),
style: ElevatedButton.styleFrom(
primary: Colors.white,
onPrimary: Colors.black,
padding: const EdgeInsets.all(16)),
onPressed: () => setState(() {
if (_curStep > 1) _curStep--;
}),
),
),
],
)
],
body: CustomeStepper(
icons: _stepIcons,
width: MediaQuery.of(context).size.width,
curStep: _curStep,
titles: _titles,
circleActiveColor: Colors.green,
circleInactiveColor: const Color(0xffD5D5D5),
iconActiveColor: Colors.white,
iconInactiveColor: Colors.white,
textActiveColor: Colors.green,
textInactiveColor: const Color(0xffD5D5D5),
content: _contnet,
),
);
}
}
I had the same problem, It would help to see your forms widgets. I will try my best to describe what you need to do.
Your textfields in your forms should be tied to your model class. Example: onChange: Product.title = TextField.value. and you should use initial value with your model properties, example: initialValue: Product.title. I think this way you can retain the state of the inputs in your forms.
As for the second part of your question, the Main widget that is controlling the stepper should have a state variable, such as isCompleted, on the last step you set this variable to 'true' and the main body of the stepper should be in a stack, in your stack you check if "isCompleted" ? Stepper : SummaryWidget.
How are handling Arabic titles for text fields and matching them with your class model properties?
I hope my answer can help!

Unsupported operation: Cannot add to an unmodifiable list

I have an app that has a splash screen and an onboarding screen. There are no errors or warnings anywhere; the app runs to show the splash screen but then crashes instead of displaying the onboarding screen.
======== Exception caught by widgets library =======================================================
The following UnsupportedError was thrown building BoardingPage(dirty, state: _BoardingScreenState#e3368):
Unsupported operation: Cannot add to an unmodifiable list
The relevant error-causing widget was:
BoardingPage BoardingPage:file:///C:/Users/Srishti/AndroidStudioProjects/App-mini-project-1/lib/splash.dart:22:78
Here's my code :
splash.dart
import 'package:flutter/material.dart';
import 'boarding_screen.dart';
class Splash extends StatefulWidget {
const Splash({Key? key}) : super(key: key);
#override
State<Splash> createState() => _SplashState();
}
class _SplashState extends State<Splash> {
#override
void initState(){
super.initState();
_navigatetohome();
}
_navigatetohome() async {
await Future.delayed(Duration(milliseconds: 2500), (){});
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => BoardingPage()));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Text('Your Scheduler',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.normal,
),
),
),
),
);
}
}
slide.dart
class Slide {
String image;
String heading;
Slide(this.image, this.heading);
}
boarding_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gradient_widgets/gradient_widgets.dart';
import 'package:schedule_management/slide.dart';
import 'login_screen.dart';
class BoardingPage extends StatefulWidget {
const BoardingPage({Key? key}) : super(key: key);
#override
_BoardingScreenState createState() => _BoardingScreenState();
}
class _BoardingScreenState extends State<BoardingPage> {
int _currentPage = 0;
List<Slide> _slides = [];
PageController _pageController = PageController();
#override
void initState() {
_currentPage = 0;
_slides = [
Slide("images/slide-1.png", "Manage your time"),
Slide("images/slide-2.png", "Schedule your tasks"),
Slide("images/slide-3.png", "Never miss out on any task"),
];
_pageController = PageController(initialPage: _currentPage);
super.initState();
}
// the list which contain the build slides
List<Widget> _buildSlides() {
return _slides.map(_buildSlide).toList();
}
// building single slide
Widget _buildSlide(Slide slide) {
return Column(
children: <Widget>[
Expanded(
child: Container(
margin: const EdgeInsets.all(1),
child: Image.asset(slide.image, fit: BoxFit.contain),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 70),
child: Text(
slide.heading,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(
height: 230,
)
],
);
}
// handling the on page changed
void _handlingOnPageChanged(int page) {
setState(() => _currentPage = page);
}
// building page indicator
Widget _buildPageIndicator() {
Row row = Row(mainAxisAlignment: MainAxisAlignment.center, children: const []);
for (int i = 0; i < _slides.length; i++) {
row.children.add(_buildPageIndicatorItem(i));
if (i != _slides.length - 1) {
row.children.add(const SizedBox(
width: 12,
));
}
}
return row;
}
Widget _buildPageIndicatorItem(int index) {
return Container(
width: index == _currentPage ? 8 : 5,
height: index == _currentPage ? 8 : 5,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: index == _currentPage
? const Color.fromRGBO(136, 144, 178, 1)
: const Color.fromRGBO(206, 209, 223, 1)),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: <Widget>[
PageView(
controller: _pageController,
onPageChanged: _handlingOnPageChanged,
physics: BouncingScrollPhysics(),
children: _buildSlides(),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Column(
children: <Widget>[
_buildPageIndicator(),
SizedBox(height: 32,),
Container(
// see the page indicators
margin: EdgeInsets.symmetric(horizontal: 10000000),
child: SizedBox(
width: double.infinity,
child: GradientButton(
callback: () => {},
gradient: LinearGradient(colors: const [
Color.fromRGBO(11, 198, 200, 1),
Color.fromRGBO(68, 183, 183, 1)
]),
elevation: 0,
increaseHeightBy: 28,
increaseWidthBy: double.infinity,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: Text(
"",
style: TextStyle(
letterSpacing: 4,
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
)),
),
SizedBox(height: 10,),
CupertinoButton(
child: Text(
"Sign In",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Colors.grey,
),
),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginScreen()));
}),
SizedBox(height: 30,),
],
),
)
],
),
);
}
}
I have tried restarting Android Studio and running flutter clean but the app still crashes.
The problem probably because of the following code:
Widget _buildPageIndicator() {
Row row = Row(mainAxisAlignment: MainAxisAlignment.center, children: const []);
for (int i = 0; i < _slides.length; i++) {
row.children.add(_buildPageIndicatorItem(i));
if (i != _slides.length - 1) {
row.children.add(const SizedBox(
width: 12,
));
}
}
return row;
}
where you're trying to change the const row children. So, change it like the following code:
Widget _buildPageIndicator() {
List<Widget> children = [];
for (int i = 0; i < _slides.length; i++) {
children.add(_buildPageIndicatorItem(i));
if (i != _slides.length - 1) {
children.add(const SizedBox(
width: 12,
));
}
}
return Row(mainAxisAlignment: MainAxisAlignment.center,
children: children,
);
}

Flutter Slide Transition To a Specific Location

I'm making a grammar quiz app using flutter, I have a question and a couple of choices, I want to make the choice slides to the empty space part of the question with a slide animation
For example:
How is _ new School?
(You) (Your) (It)
and when I press on (Your) the choice widget slides to the _ leaving an empty container
How is (Your) new School?
(You) ( ) (It)
I Made it with Draggable and DragTarget and you can see it in these images
image 1
image 2
but I want it to slide when I press on it without dragging and dropping
here is some of the code
class QuestionsScreen extends StatefulWidget {
QuestionsScreen({Key key}) : super(key: key);
#override
_QuestionsScreenState createState() => _QuestionsScreenState();
}
class _QuestionsScreenState extends State<QuestionsScreen> {
String userAnswer = "_";
int indexOfDragPlace = QuestionBrain.getQuesitonText().indexOf("_");
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(10),
color: Colors.white,
child: Center(
child: Scrollbar(
child: ListView(
children: [
Center(
child: Wrap(
children: [
...QuestionBrain.getQuesitonText()
.substring(0, indexOfDragPlace)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
_buildDragTarget(),
...QuestionBrain.getQuesitonText()
.substring(indexOfDragPlace + 1)
.split(" ")
.map((e) => QuestionHolder(
question: e + " ",
)),
],
),
)
],
),
),
),
),
Wrap(
children: [
...QuestionBrain.choices.map((choice) {
if (choice == userAnswer) {
return ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
);
}
return DraggableChoiceBox(
choice: choice,
userAnswer: userAnswer,
onDragStarted: () {
setState(() {
dragedAnswerResult = "";
});
},
onDragCompleted: () {
setState(() {
userAnswer = choice;
setState(() {
answerColor = Colors.orange;
});
print("Called");
});
},
);
}).toList()
],
),
],
),
),
);
}
Widget _buildDragTarget() {
return DragTarget<String>(
builder: (context, icoming, rejected) {
return Material(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
width: MediaQuery.of(context).size.width * 0.20,
height: MediaQuery.of(context).size.height * 0.05,
color: answerColor,
child: FittedBox(
child: Text(
userAnswer,
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
);
},
onAccept: (data) {
userAnswer = data;
answerColor = Colors.orange;
},
);
}
}
class DraggableChoiceBox extends StatelessWidget {
const DraggableChoiceBox({
Key key,
this.choice,
this.userAnswer,
this.onDragCompleted,
this.onDragStarted,
}) : super(key: key);
final String choice;
final String userAnswer;
final Function onDragCompleted;
final Function onDragStarted;
#override
Widget build(BuildContext context) {
return Draggable(
onDragCompleted: onDragCompleted,
data: choice,
child: ChoiceHolder(choice: choice),
feedback: Material(
elevation: 20,
child: ChoiceHolder(
choice: choice,
margin: 0,
),
),
childWhenDragging: ChoiceHolder(
choice: "",
backGroundColor: Colors.black12,
),
onDragStarted: onDragStarted,
);
}
}
You can use overlays similar to the way Hero widgets work, here is an "unpolished" example:
import 'package:flutter/material.dart';
class SlideToPosition extends StatefulWidget {
#override
_SlideToPositionState createState() => _SlideToPositionState();
}
class _SlideToPositionState extends State<SlideToPosition> {
GlobalKey target = GlobalKey();
GlobalKey toMove = GlobalKey();
double dx = 0.0, dy = 0.0, dxStart = 0.0, dyStart = 0.0;
String choosedAnswer = '', answer = 'answer', finalAnswer = '';
OverlayEntry overlayEntry;
#override
void initState() {
overlayEntry = OverlayEntry(
builder: (context) => TweenAnimationBuilder(
duration: Duration(milliseconds: 500),
tween:
Tween<Offset>(begin: Offset(dxStart, dyStart), end: Offset(dx, dy)),
builder: (context, offset, widget) {
return Positioned(
child: Material(
child: Container(
color: Colors.transparent,
height: 29,
width: 100,
child: Center(child: Text(choosedAnswer)))),
left: offset.dx,
top: offset.dy,
);
},
),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
SizedBox(
height: 20,
),
Row(
children: [
Text('text'),
Container(
key: target,
height: 30,
width: 100,
child: Center(child: Text(finalAnswer)),
decoration:
BoxDecoration(border: Border(bottom: BorderSide())),
),
Text('text')
],
),
SizedBox(
height: 20,
),
GestureDetector(
child: Container(
height: 30,
width: 100,
color: Colors.blue[200],
child: Center(child: Text(answer, key: toMove))),
onTap: () async {
setState(() {
answer = '';
});
RenderBox box1 = target.currentContext.findRenderObject();
Offset targetPosition = box1.localToGlobal(Offset.zero);
RenderBox box2 = toMove.currentContext.findRenderObject();
Offset toMovePosition = box2.localToGlobal(Offset.zero);
setState(() {
answer = '';
choosedAnswer = 'answer';
});
dxStart = toMovePosition.dx;
dyStart = toMovePosition.dy;
dx = targetPosition.dx;
dy = targetPosition.dy;
Overlay.of(context).insert(overlayEntry);
setState(() {});
await Future.delayed(Duration(milliseconds: 500));
overlayEntry.remove();
setState(() {
finalAnswer = 'answer';
});
},
),
],
),
),
);
}
}
Sorry for the poor naming of the variables :)

How to implement a table with fixed headers in Flutter?

How to implement a table with fixed vertical and horizontal headers in Flutter? For example, the horizontal header should only scroll horizontally and the vertical header vertically. Both headers should always be visible. How to set-up the layout?
Example
I already tried to use a Row with two nested Columns to set-up the overall 2x2 layout: (0, 0) empty; (0, 1) vertical header; (1,0) horizontal header, and (1, 1) data. To visualize the actual data I used GridViews for the two headers and the data. Moreover, I want to use the scroll controller to achieve the scroll behavior.
Row
Column: (0) empty, (1) GridView
Column: (0) GridView (1) GridView
Another solution I thought about was to have nested GridViews instead of the Row and the two Columns.
This code shows the first column:
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
child: Row(
children: <Widget>[
Column(
children: <Widget>[
Text("empty"), // (0,0)
Container( // (0, 1)
child: Flexible(
child: GridView.count(
controller: _vScrollController1,
crossAxisCount: 1,
childAspectRatio: 3.0,
children: List.generate(
widget.data.length,
(index) => Text("my cell")
),
),
),
),
],
),
],
),
);
However, it produces the following error message:
════════ Exception caught by rendering library
The method '>' was called on null.
Receiver: null.
Tried calling: >(1e-10).
User-created ancestor of the error-causing widget was Container.
═════════════════════════════════
Probably, some width/height properties are not properly set? How would you achieve this table layout? Thanks for your help!
There are actually existing Flutter plugins for this. Consider using one.
Here is an example taken from horizontal_data_table:
import 'package:flutter/material.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#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, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
HDTRefreshController _hdtRefreshController = HDTRefreshController();
static const int sortName = 0;
static const int sortStatus = 1;
bool isAscending = true;
int sortType = sortName;
#override
void initState() {
user.initData(100);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _getBodyWidget(),
);
}
Widget _getBodyWidget() {
return Container(
child: HorizontalDataTable(
leftHandSideColumnWidth: 100,
rightHandSideColumnWidth: 600,
isFixedHeader: true,
headerWidgets: _getTitleWidget(),
leftSideItemBuilder: _generateFirstColumnRow,
rightSideItemBuilder: _generateRightHandSideColumnRow,
itemCount: user.userInfo.length,
rowSeparatorWidget: const Divider(
color: Colors.black54,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: Color(0xFFFFFFFF),
rightHandSideColBackgroundColor: Color(0xFFFFFFFF),
verticalScrollbarStyle: const ScrollbarStyle(
thumbColor: Colors.yellow,
isAlwaysShown: true,
thickness: 4.0,
radius: Radius.circular(5.0),
),
horizontalScrollbarStyle: const ScrollbarStyle(
thumbColor: Colors.red,
isAlwaysShown: true,
thickness: 4.0,
radius: Radius.circular(5.0),
),
enablePullToRefresh: true,
refreshIndicator: const WaterDropHeader(),
refreshIndicatorHeight: 60,
onRefresh: () async {
//Do sth
await Future.delayed(const Duration(milliseconds: 500));
_hdtRefreshController.refreshCompleted();
},
htdRefreshController: _hdtRefreshController,
),
height: MediaQuery.of(context).size.height,
);
}
List<Widget> _getTitleWidget() {
return [
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: _getTitleItemWidget(
'Name' + (sortType == sortName ? (isAscending ? '↓' : '↑') : ''),
100),
onPressed: () {
sortType = sortName;
isAscending = !isAscending;
user.sortName(isAscending);
setState(() {});
},
),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: _getTitleItemWidget(
'Status' +
(sortType == sortStatus ? (isAscending ? '↓' : '↑') : ''),
100),
onPressed: () {
sortType = sortStatus;
isAscending = !isAscending;
user.sortStatus(isAscending);
setState(() {});
},
),
_getTitleItemWidget('Phone', 200),
_getTitleItemWidget('Register', 100),
_getTitleItemWidget('Termination', 200),
];
}
Widget _getTitleItemWidget(String label, double width) {
return Container(
child: Text(label, style: TextStyle(fontWeight: FontWeight.bold)),
width: width,
height: 56,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
);
}
Widget _generateFirstColumnRow(BuildContext context, int index) {
return Container(
child: Text(user.userInfo[index].name),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
);
}
Widget _generateRightHandSideColumnRow(BuildContext context, int index) {
return Row(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Icon(
user.userInfo[index].status
? Icons.notifications_off
: Icons.notifications_active,
color:
user.userInfo[index].status ? Colors.red : Colors.green),
Text(user.userInfo[index].status ? 'Disabled' : 'Active')
],
),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].phone),
width: 200,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].registerDate),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].terminationDate),
width: 200,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
],
);
}
}
User user = User();
class User {
List<UserInfo> userInfo = [];
void initData(int size) {
for (int i = 0; i < size; i++) {
userInfo.add(UserInfo(
"User_$i", i % 3 == 0, '+001 9999 9999', '2019-01-01', 'N/A'));
}
}
///
/// Single sort, sort Name's id
void sortName(bool isAscending) {
userInfo.sort((a, b) {
int aId = int.tryParse(a.name.replaceFirst('User_', '')) ?? 0;
int bId = int.tryParse(b.name.replaceFirst('User_', '')) ?? 0;
return (aId - bId) * (isAscending ? 1 : -1);
});
}
///
/// sort with Status and Name as the 2nd Sort
void sortStatus(bool isAscending) {
userInfo.sort((a, b) {
if (a.status == b.status) {
int aId = int.tryParse(a.name.replaceFirst('User_', '')) ?? 0;
int bId = int.tryParse(b.name.replaceFirst('User_', '')) ?? 0;
return (aId - bId);
} else if (a.status) {
return isAscending ? 1 : -1;
} else {
return isAscending ? -1 : 1;
}
});
}
}
class UserInfo {
String name;
bool status;
String phone;
String registerDate;
String terminationDate;
UserInfo(this.name, this.status, this.phone, this.registerDate,
this.terminationDate);
}
Actual output:
You can check other existing plugins.
table_sticky_headers
linked_scroll_controller
Also, here are some answered SO questions related to your:
Fixed column and row header for DataTable on Flutter Dart
How to create a horizontally scrolling table with fixed column in Flutter?
For other reference, you can check the blog "Flutter: Creating a two direction scrolling table with fixed head and column"