I'm making an app that looks like an WhatsApp clone, it has 3 tabs which contain different ListViews and other Widgets. So, the first tab is a ListView that displays a list of all the chats of the user. I have the chat feature working correctly with firestore.
Child of the first TabBarView is a ListView which displays the thumbnails, names and last messages for all the users. The problem is even though the widget is loading data correctly from firestore. The ListView is blank for some reason. I'm sure that it has all the correct data. When I switch tabs though and switch back to the first tab the ListView shows chats just for a second then it disappears. I have no idea why this is happening, please Help!
Here is the ListView widget that displays chats:
import 'package:classroom_app/Services/app_state.dart';
import 'package:classroom_app/Services/app_events.dart';
import 'package:classroom_app/Widgets/classroom_app.dart';
import 'package:flutter/material.dart';
class ChatList extends StatefulWidget {
final String userId;
ChatList(#required this.userId);
#override
_ChatListState createState() => _ChatListState();
}
class _ChatListState extends State<ChatList> {
Widget buildRow(BuildContext context, ChatItem chat) {
return Row(
children: <Widget>[
Container(
height: 100.0,
width: 100.0,
child: ClipOval(
child: Image.network(
chat.photoUrl,
fit: BoxFit.cover,
),
),
padding: EdgeInsets.all(12.0),
),
Column(
children: <Widget>[
Text(
chat.userName,
style: TextStyle(fontSize: 15.0),
),
/*Text(chat.displayMessage,
style: TextStyle(fontSize: 8.0, color: Colors.grey)),*/
],
),
/*Text(
chat.unseenCount.toString(),
style: TextStyle(color: Colors.white, background: Paint()),
),*/
],
);
}
#override
void didUpdateWidget(Widget oldWidget) {
print(ClassroomApp.of(context).chat.currentState.listOfUserChats.toString());
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
ClassroomApp.of(context).chat.dispatch(GetAllConversations(widget.userId));
return StreamBuilder(
stream: ClassroomApp.of(context).chat.state,
builder: (context, snapshot) {
if(snapshot.hasData) {
if(snapshot.data.listOfUserChats == [] || snapshot.data.listOfUserChats == null) {
return Center(child: Text('You don\'t have any chats. Try adding a Classmate.'),);
} else {
return ListView.builder(
itemBuilder: (context, index) => buildRow(context, snapshot.data.listOfUserChats[index]),
itemCount: snapshot.data.listOfUserChats.length,
);
}
} else {
return Center(child: CircularProgressIndicator(),);
}
},
);
}
}
And here is my homepage widget which contians the TabBarView.
import 'package:classroom_app/Services/app_state.dart';
import 'package:classroom_app/Widgets/chat_list.dart';
import 'package:classroom_app/Widgets/classroom_app.dart';
import 'package:classroom_app/Services/app_events.dart';
import 'package:flutter/material.dart';
enum UserOptions { AddClassmate, Settings, SignOut }
class HomePage extends StatefulWidget {
HomePage({Key key, #required this.title, #required this.userAuth})
: super(key: key);
final String title;
final AuthenticationState userAuth;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
final List<Tab> appBarTabs = [
Tab(text: 'CHATS'),
Tab(text: 'LECTURES'),
Tab(text: 'DOUBTS'),
];
String _userName;
TabController _tabController;
TextEditingController _editingController;
#override
void initState() {
_tabController = TabController(vsync: this, length: appBarTabs.length);
_editingController = TextEditingController();
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(fontSize: 25, color: Colors.
bottom: TabBar(
labelColor: Colors.white,
controller: _tabController,
tabs: appBarTabs,
isScrollable: false,
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Container(
child: ChatList(widget.userAuth.user.uid),
),
Center(child: Text('This is lectures view.')),
Center(child: Text('This is doubts view.')),
],
),
);
}
}
I've added a GIF, to show the behaviour of ListView
I'm also adding code for the chatBloc component as well as the ClassroomApp InheritedWidget
class ChatBloc extends Bloc<ChatEvent, ChatState> {
final ChatService _chatService = ChatService();
#override
ChatState get initialState => ChatState(null, null);
#override
Stream<ChatState> mapEventToState(
ChatState currentState, ChatEvent event) async* {
if (event is StartConversation) {
_chatService.startNewConversation(event.senderId, event.recieverName);
yield ChatState(
await _chatService.getAllMessagesByUserId(event.senderId), null);
}
if (event is GetAllConversations) {
List<ChatItem> chatList =
await _chatService.getAllMessagesByUserId(event.userId);
yield ChatState(chatList, null);
}
}
}
ClassroomApp InheritedWidget:
import 'package:flutter/material.dart';
import 'package:classroom_app/Services/bloc_serviec.dart';
class ClassroomApp extends InheritedWidget {
final AuthBloc auth = AuthBloc();
final ChatBloc chat = ChatBloc();
ClassroomApp({Widget child}) : super(child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static ClassroomApp of(BuildContext context) =>
context.inheritFromWidgetOfExactType(ClassroomApp);
}
Related
I am working with flutter. I create a DropDown menu in the menu_list.dart file. I want to use the value selected from the user in (menu_list.dart file) in the add_screen.dart file. So, that I can upload it in the FireStore with other user information. The code is attached below.
I am glad if someone helps.
'add_Screen.dart'
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:database/widgets/menu_list.dart';
import 'package:flutter/material.dart';
class AddScreen extends StatelessWidget {
String? personname, personphone, vall;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add Data"),
),
body: Column(
children: [
Container(
child: MenuList(),
),
SizedBox(
height: 15,
),
Container(
child: TextFormField(
onChanged: (String name) {
getStudentName(name);
},
decoration: InputDecoration(
labelText: "Name",
focusedBorder: OutlineInputBorder(),
),
),
),
SizedBox(
height: 15,
),
Container(
child: TextFormField(
onChanged: (String phone) {
getStudentPhone(phone);
},
decoration: InputDecoration(
labelText: "PhoneNumber",
focusedBorder: OutlineInputBorder(),
),
),
),
SizedBox(
height: 15,
),
Container(
child: RaisedButton(
child: Text("Add"),
onPressed: createData,
),
),
],
),
);
}
createData() {
Future<void> documentReference = FirebaseFirestore.instance
.collection("Students")
.doc("subcollection")
.collection("collectionPath")
.doc()
.set(
{
"PersonName": personname,
"PersonPhone": personphone,
},
);
}
getStudentName(name) {
this.personname = name;
}
getStudentPhone(phone) {
this.personphone = phone;
}
}
`
----------------------------------------------------------------------------------------------
'menu_list.dart'
`
import 'package:flutter/material.dart';
class MenuList extends StatefulWidget {
const MenuList({Key? key}) : super(key: key);
#override
_MenuListState createState() => _MenuListState();
}
class _MenuListState extends State<MenuList> {
final items = ['Maths', 'Urdu', 'English', 'Simple'];
String? value;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: value,
isExpanded: true,
items: items.map(buildMenuItem).toList(),
onChanged: (value) => setState(
() => this.value = value,
),
);
}
}
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 30,
),
),
);
`
I think the best option for you, without overegineering the code, would be to pass a callback function that is received as parameter for the onChanged parameter of the DropdownButton.
It would look something like this:
In add_screen:
String _itemSelected;
MenuList(onChanged: (value) {
_itemSelected = value;
})
In menu_list:
class MenuList extends StatefulWidget {
final Function(String) onChanged;
const MenuList({required this.onChanged, Key? key}) : super(key: key);
#override
_MenuListState createState() => _MenuListState();
}
class _MenuListState extends State<MenuList> {
final items = ['Maths', 'Urdu', 'English', 'Simple'];
String? value;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: value,
isExpanded: true,
items: items.map(buildMenuItem).toList(),
onChanged: widget.onChanged
);
}
}
I apologize if the code doesn't compile directly since I'm not able to compile it at the time.
Feel free to reach me if you have any questions
class AddScreen extends StatefulWidget {
#override
_AddScreenState createState() => _AddScreenState();
}
class _AddScreenState extends State<AddScreen> {
// make stateful
String? personname, personphone, vall, dropdownValue;
// define value for dropdownValue
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add Data"),
),
body: Column(
children: [
Container(
child: MenuList(dropdownValue: dropdownValue),
// call MenuList as above passing dropdown value;
),
...
// other children
],
),
);
}
}
// menu_list.dart
import 'package:flutter/material.dart';
class MenuList extends StatefulWidget {
String dropdownValue;
// pass the value here
MenuList({Key? key, this.dropdownValue}) : super(key: key);
#override
_MenuListState createState() => _MenuListState();
}
class _MenuListState extends State<MenuList> {
final items = ['Maths', 'Urdu', 'English', 'Simple'];
// String? value;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: widget.dropdownValue,
// set the value here
isExpanded: true,
items: items.map(buildMenuItem).toList(),
onChanged: (value) => setState(
() => widget.dropdownValue = value,
// set the value here
),
);
}
}
I am a new user in a flutter, I am fetching a value from a FireStore (majname), and I have two tabs in the same file, I want to pass the value to the two tabs.
I have searched and found no way to do that, can you help me and explain to me how I will do it?
I have searched and found no way to do that, can anyone help me and explain to me how I will do it?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:gradient_app_bar/gradient_app_bar.dart';
class Files extends StatefulWidget {
#override
_FilesState createState() => _FilesState();
}
class _FilesState extends State<Files> {
var my_uid;
var majname;
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then((user) {
setState(() {
my_uid = user.uid;
print(user.uid);
});
Firestore.instance
.collection('users')
.where('uid', isEqualTo: my_uid.toString())
.getDocuments()
.then((docs) {
Firestore.instance
.document('/users/${docs.documents[0].documentID}')
.get()
.then((val) {
setState(() {
majname = val.data['majname'];
});
}).catchError((e) {
print(e);
});
});
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 3,
child: Scaffold(
appBar: GradientAppBar(
automaticallyImplyLeading: true,
backgroundColorStart: Colors.deepPurple,
backgroundColorEnd: Colors.purple,
title: Text(
'Files Manager',
style: TextStyle(fontWeight: FontWeight.bold),
),
bottom: TabBar(
isScrollable: true,
tabs: <Widget>[
Tab(
icon: new Icon(Icons.person_pin),
text: "My Files",
),
Tab(
icon: new Icon(Icons.school),
text: "Major Files",
),
],
),
),
body: TabBarView(
children: <Widget>[
MyFiles(),
MajFiles(),
],
),
),
);
}
}
// My
class MyFiles extends StatefulWidget {
#override
_MyFilesState createState() {
return _MyFilesState();
}
}
class _MyFilesState extends State<MyFiles> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Center(
child: Text('My'),
),
],
);
}
}
// Maj
class MajFiles extends StatefulWidget {
#override
_MajFilesState createState() {
return _MajFilesState();
}
}
class _MajFilesState extends State<MajFiles> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Center(
child: Text('Maj'),
),
],
);
}
}
Here is my class
class Home extends StatelessWidget {
and the Checkbox goes here.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(
controller: ctrlMotherName,
decoration: InputDecoration(
labelText: "Name of Mother",
border: OutlineInputBorder()
)
),
SizedBox(height: 10,),
Checkbox(
value: false,
onChanged: (bool val){
},
),
I can't able to check the checkbox. Same issue found when I use Radiobutton also.
You need to use a StatefulWidget since you're dealing with changing values. I've provided an example:
class MyAppOne extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyAppOne> {
bool _myBoolean = false;
#override
Widget build(BuildContext context) {
return Center(
child: Checkbox(
value: _myBoolean,
onChanged: (value) {
setState(() {
_myBoolean = value; // rebuilds with new value
});
},
),
);
}
}
One way you can achieve this is using the provider package. I tried to create the shortest possible app to show how you can use it. The neat part is that you get to keep your widget stateless.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Home(),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ChangeNotifierProvider(
create: (_) => CheckboxProvider(),
child: Consumer<CheckboxProvider>(
builder: (context, checkboxProvider, _) => Checkbox(
value: checkboxProvider.isChecked,
onChanged: (value) {
checkboxProvider.isChecked = value ?? true;
},
),
),
),
),
);
}
}
class CheckboxProvider with ChangeNotifier {
bool _isChecked = false;
bool get isChecked => _isChecked;
set isChecked(bool value) {
_isChecked = value;
notifyListeners();
}
}
It took me quite some time to understand the package but it is very useful and recommended if you want an easier way to manage state in your application. Here's a video from the Flutter team explaining how to use Provider. I would still recommend spending some time looking further into it.
P.S.: Don't forget to change the pubspec.yaml file.
think of flutter like javascript and pass the position as a parameter to the medCheckedChanged function in the list builder. when the dart parser evaluates the expression or lambda function it will invoke the method with the position parameter as a value.
class testWidget2 extends StatefulWidget {
testWidget2({Key key}) : super(key: key);
int numberLines = 50;
List<bool> checkBoxValues = [];
#override
_testWidget2State createState() => _testWidget2State();
}
class _testWidget2State extends State<testWidget2> {
_medCheckedChanged(bool value, int position) {
setState(() => widget.checkBoxValues[position] = value);
}
#override
void initState() {
// TODO: implement initState
super.initState();
int i = 0;
setState(() {
while (i < widget.numberLines) {
widget.checkBoxValues.add(false);
i++;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ListView.builder(
itemCount: widget.checkBoxValues.length,
itemBuilder: (context, position) {
return Container(
height: MediaQuery.of(context).size.width * .06,
width: MediaQuery.of(context).size.height * .14,
alignment: Alignment(0, 0),
child: Checkbox(
activeColor: Color(0xff06bbfb),
value: widget.checkBoxValues[position],
onChanged: (newValue) {
_medCheckedChanged(newValue, position);
}, //pass to medCheckedChanged() the position
),
);
})));
}
}
You can use StateProvider from the Riverpod package to achieve this.
final checkboxProvider = StateProvider<bool>((ref) => false);
class CheckboxWidget extends StatelessWidget {
const CheckboxWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
Consumer(
builder: (context, ref, _) {
return Checkbox(
value: ref.watch(checkboxProvider),
onChanged: (value) {
ref.read(checkboxProvider.state).state = value!;
},
);
}
),
Text('On'),
],
);
}
}
I am building an app and in it, I have the names of people in a list from which I could add/delete, etc.. The problem is this list is not saved when I close the app, which is inconvenient.
I heard you can use shared Preferences to save simple objects like this, without complicating things like using SQLite and json.
So I'd like to know what's the suggested way to persist this data and load it etc.
Thanks in Advance and have a great day :)
Here is the code:
import 'package:flutter/material.dart';
import 'package:zakif_yomi3/NewPerson.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
),
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> {
final List<String> people = [];
void _addNewPerson(String name) {
setState(() {
people.add(name);
});
}
void _startAddNewPerson(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
child: NewPerson(_addNewPerson),
behavior: HitTestBehavior.opaque,
);
},
);
}
void _deletePerson(int value ) {
setState(() {
people.removeAt(value);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
'People',
style: TextStyle(fontSize: 30),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () => _startAddNewPerson(context),
)
],
),
body: ListView.builder(
itemCount: this.people.length,
itemBuilder: (context, value) {
return Card(
color: Colors.amberAccent[200],
elevation: 3,
child: Container(
child: ListTile(
leading: Text(value.toString()),
title: Text(
people[value],
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_deletePerson(value);
},
),
),
),
);
},
),
);
}
}
And the NewPerson object:
import 'package:flutter/material.dart';
class NewPerson extends StatefulWidget {
final Function addTx;
NewPerson(this.addTx);
#override
_NewPersonState createState() => _NewPersonState();
}
class _NewPersonState extends State<NewPerson> {
final _nameController = TextEditingController();
void _submitData() {
final name = _nameController.text;
widget.addTx(
name
);
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Card(
elevation: 5,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Name'),
controller: _nameController,
onSubmitted: (_) => _submitData(),
),
RaisedButton(
child: Text('Add Person'),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).textTheme.button.color,
onPressed: _submitData,
),
],
),
),
);
}
}
You could use this functions to persist and load data from shared preferences.
Get SharedPreferences from here.
To persist data to SharedPreferences, called after adding or deleting a new element to the list.
_persistData() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setStringList("persons", _people);
}
To load data from SharedPreferences, usually called in initState.
_loadData() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
setState(() {
_people = preferences.getStringList("persons");
});
}
I'm trying to build a Tabbed View that has lists as children.
Both the Category labels and the lists content will be fetched from a database.
I am passing the labels from the caller page and successfully passing them as a List.
Now I'm trying to load my lists, and I have built a Widget (myList) that returns successfully a Future ListView.
The problems are two:
Every time i swipe left or right, the list rebuilds itself, while I would like to have it built only once
How can I use the code I made to have the tabs' children actually reflect the labels and are loaded dinamically according to how many categories i have?
Right now my code is this:
import 'package:flutter/material.dart';
import 'package:flutter_app/ui/menu_category_list.dart';
// Each TabBarView contains a _Page and for each _Page there is a list
// of _CardData objects. Each _CardData object is displayed by a _CardItem.
List<Tab> Tabs(List<String> l){
List<Tab> list;
for (String c in l) {
list.add(new Tab(text: c));
}
return list;
}
class TabsDemo extends StatelessWidget {
const TabsDemo({ Key key , this.categorie}) : super(key: key);
final List<Tab> categorie;
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
title: "Nice app",
home: new DefaultTabController(
length: 5,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
bottom: new TabBar(
tabs:
categories,
//new Tab(text: "First Tab"),
//new Tab(text: "Second Tab"),
),
),
body: new TabBarView(
children: [
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList(),
new MenuCategoryList()
]
)
),
)
);
}
}
currently result
Thanks a lot in advance
You can use List<E>.generate to achieve this.
import 'package:flutter/material.dart';
Say you have a set of categories passed from your caller page. And let's say this is your list of categories.
List<String> categories = ["a", "b", "c", "d", "e", "f", "g", "h"];
Then you can do something like this to achieve what you desire.
class TabsDemo extends StatefulWidget {
#override
_TabsDemoState createState() => _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo> {
TabController _controller;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
home: DefaultTabController(
length: categories.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
bottom: new TabBar(
isScrollable: true,
tabs: List<Widget>.generate(categories.length, (int index){
print(categories[0]);
return new Tab(icon: Icon(Icons.directions_car), text: "some random text");
}),
),
),
body: new TabBarView(
children: List<Widget>.generate(categories.length, (int index){
print(categories[0]);
return new Text("again some random text");
}),
)
))
);
}
You can also set different set of widgets as the Tab's view. You can create a list of pages and follow the same method.
Absolutely true List<E>.generate best solution to solve.
Problems arise if you need to modify the arrays. They consist in the fact that when modifying an array you do not have the opportunity to use the same controller.
You can use the next custom widget for this case:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> data = ['Page 0', 'Page 1', 'Page 2'];
int initPosition = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomTabView(
initPosition: initPosition,
itemCount: data.length,
tabBuilder: (context, index) => Tab(text: data[index]),
pageBuilder: (context, index) => Center(child: Text(data[index])),
onPositionChange: (index){
print('current position: $index');
initPosition = index;
},
onScroll: (position) => print('$position'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
data.add('Page ${data.length}');
});
},
child: Icon(Icons.add),
),
);
}
}
/// Implementation
class CustomTabView extends StatefulWidget {
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget stub;
final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll;
final int initPosition;
CustomTabView({
#required this.itemCount,
#required this.tabBuilder,
#required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
TabController controller;
int _currentCount;
int _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
_currentCount = widget.itemCount;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 :
_currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_){
if(mounted) {
widget.onPositionChange(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value);
}
}
}
You can use dynamic children using for loop within your Tabbarview Widget
List<String> categories = ["category 1" , "category 2", "category 3",];
return TabBarView(
children:[
for(var category in categories)
Text(category), // this widget will show a text with specific category. You can use any other widget
],
);
Null safety version
import 'package:flutter/material.dart';
class CustomTabView extends StatefulWidget {
final int? itemCount;
final IndexedWidgetBuilder? tabBuilder;
final IndexedWidgetBuilder? pageBuilder;
final Widget? stub;
final ValueChanged<int>? onPositionChange;
final ValueChanged<double>? onScroll;
final int? initPosition;
CustomTabView({this.itemCount, this.tabBuilder, this.pageBuilder, this.stub,
this.onPositionChange, this.onScroll, this.initPosition});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
late TabController controller;
late int _currentCount;
late int _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition!;
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
_currentCount = widget.itemCount!;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition!;
}
if (_currentPosition > widget.itemCount! - 1) {
_currentPosition = widget.itemCount! - 1;
_currentPosition = _currentPosition < 0 ? 0 :
_currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance!.addPostFrameCallback((_){
if(mounted) {
widget.onPositionChange!(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount!;
setState(() {
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation!.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition!);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation!.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount! < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount!,
(index) => widget.tabBuilder!(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount!,
(index) => widget.pageBuilder!(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange!(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll!(controller.animation!.value);
}
}
}