I am making an app and I working on the onboarding screen. When the user finishes answering their questions, I want them the screen to navigate to their user dashboard. I am using a tab controller for the onboarding screen setup, and the problem that I am facing is the final screen not navigating when it is pressed.
I receive the following error when I press the button:
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: 'package:flutter/src/material/tab_controller.dart': Failed assertion: line 181 pos 12: 'value >= 0 && (value < length || length == 0)': is not true.
My code is set up to check if the tab controller index is on the last page. I understand why it is coming up as false; however, I do not know how else to code it to overcome this issue. I appreciate any help given :)
Code for button and tab controller:
class CustomButton extends StatelessWidget {
final TabController tabController;
const CustomButton({Key? key,
required this.tabController})
: super(key: key);
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.white
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
vertical: 16),
elevation: 0,
primary: Colors.transparent
),
onPressed: () async{
if (tabController.index == 4) {
Navigator.pushNamed(context, '/dashboard');
}
else {
tabController.animateTo(tabController.index + 1);
}
// await context.read<SignupCubit>().signupWithCredentials();
User user = User(
id: context.read<SignupCubit>().state.user!.uid,
name: '',
age: 0,
imageUrls: [],
Goals: '',
Interests: [],
Focus: []);
},
child: Container(
width: double.infinity,
child: Center(
child: Text('Continue',
style: GoogleFonts.montserrat(
color: const Color.fromARGB(255, 20, 83, 106),
fontSize: 19,
fontWeight: FontWeight.w600
),),
),
)
),
);
}
}
where is your tabbarview ?
try this steps https://www.woolha.com/tutorials/flutter-using-tabbar-tabbarview-examples
or if you want to understand and implement try this
https://youtu.be/apqVp-UjsLg
Related
I'm using the flutter markdown package made by the flutter team here https://pub.dev/packages/flutter_markdown. I've created my own MarkdownElementBuilder based on their examples that inserts my own custom widget into the markdown and it looks like this:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:outlit_app/constants/color_theme.dart';
import 'package:outlit_app/constants/dimension.dart';
import 'package:outlit_app/models/models.dart';
import 'package:markdown/markdown.dart' as md;
class DefinitionBuilder extends MarkdownElementBuilder {
final List<Definition> definitions;
DefinitionBuilder(this.definitions) : super();
#override
Widget visitElementAfter(md.Element element, TextStyle preferredStyle) {
final String textContent = element.textContent;
Definition definition = definitions.firstWhere(
(def) => textContent.toLowerCase().contains(def.word.toLowerCase()),
orElse: () =>
Definition(word: 'nothing found for $textContent', definition: ''),
);
return Tooltip(
margin: EdgeInsets.all(Dimensions.MARGIN_SIZE_EXTRA_LARGE),
padding: EdgeInsets.all(Dimensions.PADDING_SIZE_DEFAULT),
decoration: BoxDecoration(
color: GetColor.gradientPurple,
borderRadius: BorderRadius.circular(8),
),
verticalOffset: -10,
triggerMode: TooltipTriggerMode.tap,
message: definition.definition.trim(),
child: Text(
textContent.trim(),
style: TextStyle(
color: GetColor.primaryColor,
fontSize: Dimensions.FONT_SIZE_OVER_LARGE,
),
),
);
}
}
class DefinitionSyntax extends md.InlineSyntax {
static final String AST_SYMBOL = 'def';
DefinitionSyntax() : super(_pattern);
static const String _pattern = r'{{(.*)}}';
#override
bool onMatch(md.InlineParser parser, Match match) {
parser.addNode(md.Element.text(AST_SYMBOL, match[1]));
return true;
}
}
It works well but the widget is always on it's own seperate line as opposed to being inline with the rest of the text. If I return a simple text widget I still get the same thing.
Any tips in the right direction would be great :)
I got it work although not perfect because the leading distribution is a little off with the text of the tooltip but the widget that gets embedded now looks like this:
return RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Container(
child: Tooltip(
margin: EdgeInsets.all(Dimensions.MARGIN_SIZE_EXTRA_LARGE),
padding: EdgeInsets.all(Dimensions.PADDING_SIZE_DEFAULT),
decoration: BoxDecoration(
color: GetColor.gradientPurple,
borderRadius: BorderRadius.circular(8),
),
verticalOffset: -10,
triggerMode: TooltipTriggerMode.tap,
message: definition.definition.trim(),
child: Text(
textContent.trim(),
style: TextStyle(
color: GetColor.primaryColor,
fontSize: Dimensions.FONT_SIZE_OVER_LARGE,
leadingDistribution: TextLeadingDistribution.even,
height: 1,
),
),
),
))
],
),
);
I am trying to create a favorite button for my app. Which work is to change and save color, while the user press it, So I decided to use hive db for it. The problem is, when i tap on the button; the color get changed, but when i move to other page or hot start/reload the page, the color changed back to it former self automatically.How to solve this problem and create a favorite button successfully.
class p1 extends StatefulWidget {
#override
_p1State createState() => _p1State();
}
class _p1State extends State<p1> {
Box box;
_p1State();
#override
void initstate(){
super.initState();
// Get reference to an already opened box
box = Hive.box(FAVORITES_BOX);
}
#override
void dispose() {
// Closes all Hive boxes
Hive.close();
super.dispose();
}
get_info(){
var info = box.get(_isFavorite);
}
var _isFavorite = true;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body:Stack(
children:<Widget>[
Image(
image:AssetImage("Image/Chowsun1.jpg"),
fit:BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
Align(alignment: Alignment.center,
child: Text(' Rehman '
,style: TextStyle(fontSize: 35.0,
color: Colors.white,
fontFamily: "Explora",
fontWeight: FontWeight.w900 ) )
),
Stack ( children: [Positioned(
top:90,
right: 20,
child:const Text(' 1 ',
style: TextStyle(
fontSize: 25.0,
color: Colors.white,
fontFamily: "Comforter"
),
),
)], ),
Align(
alignment: Alignment.bottomCenter,
child: (
IconButton(
icon: Icon(
Icons.favorite,
color:_isFavorite ? Colors.white: Colors.red
),
onPressed: () {
setState(() {
_isFavorite= !_isFavorite;
});
box.put(!_isFavorite, _isFavorite);
get_info();
}
)
)
)])
),
);
}
}
There are a lot of things that don't make sense in this code.
I believe that the flow should be like this.
Your Widget first initialize the hive box and check if there is any data to refer to.
If there is a data already written, load it, or else, set a default value, which I bet is false. Because everything is not favorite before you make it so.
When you click Favorite button, it updates _isFavorite value and put it in the open Box with a key which must be a unique value that can represent the selected item right
There are problems in your code:
You initialized the Box in initState() but you didn't consider if there is data already in Box. So you have to check and if there is data you can use, you have to update _isFavorite accordingly.
You close Box every time your widget is disposed but I don't see your widget opens it again when you're back to the widget. So just don't close it. It seems like you'll use it very often.
You put the data with a key, which is the old data whose data type is boolean. It's binary so it can't be used as an unique key.
So if I were you, I would fix the code like following:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
class p1 extends StatefulWidget {
#override
_p1State createState() => _p1State();
}
class _p1State extends State<p1> {
late Box box;
late bool _isFavorite;
#override
void initState() {
super.initState();
// Get reference to an already opened box.
// This should be open before you re-enter this widget.
box = Hive.box('FAVORITES_BOX');
// Try getting data that already exists
final data = box.get(someKeyYouAlreadyUsed);
// if there is no data about favorite, assign false to _isFavorite
_isFavorite = data ?? false;
}
// Don't close the Hive Box if you're planning to come back and use it soon.
// #override
// void dispose() {
// // Closes all Hive boxes
// Hive.close();
// super.dispose();
// }
// I deleted this function. It doesn't do anything.
// get_info() {
// var info = box.get(_isFavorite);
// }
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(children: <Widget>[
const Image(
image: AssetImage("Image/Chowsun1.jpg"),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
const Align(
alignment: Alignment.center,
child: Text(' Rehman ',
style: TextStyle(
fontSize: 35.0,
color: Colors.white,
fontFamily: "Explora",
fontWeight: FontWeight.w900))),
Stack(
children: const [
Positioned(
top: 90,
right: 20,
child: Text(
' 1 ',
style: TextStyle(
fontSize: 25.0,
color: Colors.white,
fontFamily: "Comforter"),
),
)
],
),
Align(
alignment: Alignment.bottomCenter,
child: (IconButton(
icon: Icon(Icons.favorite,
color: _isFavorite ? Colors.white : Colors.red),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
box.put(someUniqueValueToRepresentThisItem, _isFavorite);
})))
])),
);
}
}
I hope this would work for you.
You are setting the state of button on the basis of _isFavorite variable value instead of the value which came from the hive database (your get info function). After inserting the value in hive db you must set the state of button using hive db value instead of the _isFavorite variable.
So I have 3 tabs HEX , RGB, and HSL as StatefulWidget.
I have these 3 tabs in a Row in a StatelessWidget.
By default, HEX is selected, which I achieved successfully by initialising initState(). What I want to achieve now is that if I tap on any tab, the other two have to automatically go to a deselected state.
I used GestureDetector but it only changes the state of the tab I tapped on. I toggled the styles of all 3 tabs in the setState() function, but I guess the changes don't take place as the other 2 tabs are not built again, only the tab I tapped on is built again. By built I mean createState().
I am a beginner in Flutter, and I have spent a whole day finding solutions to this but to no avail.
I tried rebuilding the parent Row but I am not able to do so. I am trying to find a method by which I can rebuild widgets by key.
Any solution?
EDIT: The code for that section ->
key arrayKey = GlobalKey();
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++CHIPS++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class chipMaker extends StatefulWidget {
chipMaker({required this.label}) : key = ObjectKey(label), chipSelect = false;
final String label;
final Key key;
bool chipSelect;
#override
_chipState createState() => _chipState();
}
class _chipState extends State<chipMaker> {
chipMaker get chip => super.widget;
//need to add setState();
#override
Widget build(BuildContext context) {
// TODO: implement build
return GestureDetector(
onTap: () {
//function
},
child: Container(
margin: EdgeInsets.only(left: 8),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
border: Border.all(
color: defaultColor,
width: 2,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.all(Radius.circular(4)),
color: chip.chipSelect ? defaultColor : Colors.white,
),
child: Text(
chip.label,
style: TextStyle(
fontSize: 10,
height: 1.2,
color: chip.chipSelect ? Colors.white : defaultColor,
),
),
),
);
}
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++ARRAY++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class chipArray extends StatelessWidget {
chipArray({Key? arrayKey,}) : super(key: arrayKey);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Row(
children: [
chipMaker(label: 'HEX',),
chipMaker(label: 'RGB',),
chipMaker(label: 'HSL',),
],
);
}
}
I have a SliverGrid. I have a search field. In my search field onChange event I have a function that searches my local sqlite db based on the keyword entered by the user returns the results and reassigns to a variable and calls notifyListeners(). Now my problem is for some weird reason whenever I search for an item the wrong item is rendered.
I checked the results from my functions by iterating over the list and logging the title and the overall count as well and the results were correct however my view always rendered the wrong items. Not sure how this is possible.
I also noticed something strange, whenever it rendered the wrong item and I went back to my code and hit save, triggering live reload, when I switched back to my emulator it now displayed the right item.
I have tried the release build on an actual phone and it's the same behaviour. Another weird thing is sometimes certain items will duplicate and show twice in my list while the user is typing.
This is my function that searches my sqlite db:
Future<List<Book>> searchBookshelf(String keyword) async {
try {
Database db = await _storageService.database;
final List<Map<String, dynamic>> rows = await db
.rawQuery("SELECT * FROM bookshelf WHERE title LIKE '%$keyword%'; ");
return rows.map((i) => Book.fromJson(i)).toList();
} catch (e) {
print(e);
return null;
}
}
This is my function that calls the above function from my viewmodel:
Future<void> getBooksByKeyword(String keyword) async {
books = await _bookService.searchBookshelf(keyword);
notifyListeners();
}
This is my actual view where i have the SliverGrid:
class BooksView extends ViewModelBuilderWidget<BooksViewModel> {
#override
bool get reactive => true;
#override
bool get createNewModelOnInsert => true;
#override
bool get disposeViewModel => true;
#override
void onViewModelReady(BooksViewModel vm) {
vm.initialise();
super.onViewModelReady(vm);
}
#override
Widget builder(BuildContext context, vm, Widget child) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height) / 4.3;
final double itemWidth = size.width / 3;
var heading = Container(
margin: EdgeInsets.only(top: 35),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Books',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
),
Text(
'Lorem ipsum dolor sit amet.',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 14),
),
],
),
),
);
var searchField = Container(
margin: EdgeInsets.only(top: 5, left: 15, bottom: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 1.0,
spreadRadius: 0.0,
offset: Offset(2.0, 1.0), // shadow direction: bottom right
),
],
),
child: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
FlutterIcons.search_faw,
size: 18,
),
suffixIcon: Icon(
FlutterIcons.filter_fou,
size: 18,
),
hintText: 'Search...',
),
onChanged: (keyword) async {
await vm.getBooksByKeyword(keyword);
},
onFieldSubmitted: (keyword) async {},
),
);
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 1, right: 1),
child: LiquidPullToRefresh(
color: Colors.amber,
key: vm.refreshIndicatorKey, // key if you want to add
onRefresh: vm.refresh,
showChildOpacityTransition: true,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
heading,
searchField,
],
),
),
SliverToBoxAdapter(
child: SpaceY(15),
),
SliverToBoxAdapter(
child: vm.books.length == 0
? Column(
children: [
Image.asset(
Images.manReading,
width: 250,
height: 250,
fit: BoxFit.contain,
),
Text('No books in your bookshelf,'),
Text('Grab a book from our bookstore.')
],
)
: SizedBox(),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid.count(
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: vm.books
.map((book) => BookTile(book: book))
.toList(),
),
)
],
),
))));
}
#override
BooksViewModel viewModelBuilder(BuildContext context) =>
BooksViewModel();
}
Now the reason I am even using SliverGrid in the first place is because I have a search field and a title above the grid and I want all items to scroll along with the page, I didn't want just the list to be scrollable.
I believe this odd behavior can be attributed to you calling vm.getBooksByKeyword() in onChanged. As this is an async method, there is no guarantee that the last result returned will be the result for the final text in the TextFormField. The reason you see the correct results after a live reload is because the method is being called again with the full text currently in the TextFormField.
The quickest way to verify this is to move the function call to onFieldSubmitted or onEditingComplete and see if it behaves correctly.
If you require calling the function with every change to the text, you will need to add a listener to the controller and be sure to only make the call after input has stopped for a specified amount of time, using a Timer, like so:
final _controller = TextEditingController();
Timer _timer;
...
_controller.addListener(() {
_timer?.cancel();
if(_controller.text.isNotEmpty) {
// only call the search method if keyword text does not change for 300 ms
_timer = Timer(Duration(milliseconds: 300),
() => vm.getBooksByKeyword(_controller.text));
}
});
...
#override
void dispose() {
// DON'T FORGET TO DISPOSE OF THE TextEditingController
_controller.dispose();
super.dispose();
}
...
TextFormField(
controller: controller,
...
);
So I found the problem and the solution:
The widget tree is remembering the list items place and providing the
same viewmodel as it had originally. Not only that it also takes every
item that goes into index 0 and provides it with the same data that
was enclosed on the Construction of the object.
Taken from here.
So basically the solution was to add and set a key property for each list item generated:
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
),
delegate: SliverChildListDelegate(vm.books
.map((book) => BookTile(
key: Key(book.id.toString()), book: book))
.toList()),
),
)
And also here:
const BookTile({Key key, this.book}) : super(key: key, reactive: false);
My search works perfectly now. :)
I'm trying to create a simple vertical scrolling calendar.
Problem is that I can't manage to find a way to reset back to previous state in case I tap on a new container.
Here's the code:
class CalendarBox extends StatelessWidget {
BoxProprieties boxProprieties = BoxProprieties();
Map item;
CalendarBox({this.item});
bool selected = false;
#override
Widget build(BuildContext context) {
return Consumer<Producer>(
builder: (context, producer, child) => GestureDetector(
onTap: () {
print(item['dateTime']);
selected = producer.selectedState(selected);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
color: selected == true ? Colors.blue : Colors.grey[200],
height: 80,
width: 50,
margin: EdgeInsets.only(top: 5),
child: Column(
children: [
Text(
'${item['dayNum']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: boxProprieties.dayColor(item['dateTime'])),
),
],
),
),
),
);
}
}
Here's the situation:
One way to achieve it is, create a model for boxes and keep a value current selected block, in your model you will have the index assigned to that block,
int currentSelected =1; //initial value
class Block{
int id;
..
.. // any other stuff
}
now in your code, the check modifies to
block.id == currentSelected ? Colors.blue : Colors.grey[200],
your on tap modifies to
onTap: () {
setState(){
currentSelected = block.id
};
},
If you want to prevent the rebuild of the whole thing every time you can use valueNotifire for current selected block. Hope this gives you an idea.