How to rebuild IndexedStack when its children particular state changes - flutter

IndexedStack solved the problem of persisting the content when the bottom navigation index changes. But I how couldn't find a way to rebuild the IndexedStack when its children state changes. The children's state change is reflected only when the app is restarted.
IndexedStack is not updated when _children changed.
Below is the code:
Widget myBody() {
Widget myBodyWidgetData = SizedBox.shrink();
if (_isSelectedConnectionReady &&
_selectedConnectionId is int &&
_selectedConnectionId >= 0 &&
_selectedConnection.runtimeType == Connection) {
myBodyWidgetData = IndexedStack(
index: _selectedBottomNavigationIndex,
children: _children,
);
} else if (_isSelectedConnectionReady && _selectedConnectionId < 0) {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
myBodyWidgetData = RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: setSelectedConnection,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(10, 20, 10, 20),
child: Text(
"Kindly add a connection to manage woocommerce",
textAlign: TextAlign.center,
),
),
Container(
height: 45,
width: 200,
child: RaisedButton(
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddConnectionPage(
refreshConnectionsList: setSelectedConnection,
),
),
);
},
child: Text(
"Add Connection",
style: Theme.of(context).textTheme.button,
),
),
)
],
),
),
);
} else {
myBodyWidgetData = Center(
child: SpinKitPulse(
color: Theme.of(context).primaryColor,
size: 70,
),
);
}
return myBodyWidgetData;
}
Full code:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:woocommerceadmin/src/common/widgets/MyDrawer.dart';
import 'package:woocommerceadmin/src/connections/widgets/AddConnectionPage.dart';
import 'package:woocommerceadmin/src/customers/widgets/CustomersListPage.dart';
import 'package:woocommerceadmin/src/db/ConnectionDBProvider.dart';
import 'package:woocommerceadmin/src/db/models/Connection.dart';
import 'package:woocommerceadmin/src/orders/widgets/OrdersListPage.dart';
import 'package:woocommerceadmin/src/products/widgets/ProductsListPage.dart';
import 'package:woocommerceadmin/src/reports/widgets/ReportsPage.dart';
import 'package:woocommerceadmin/src/config.dart';
import 'package:flutter_spinkit/flutter_spinkit.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: 'Woocommerce Admin',
theme: ThemeData(
primarySwatch: Colors.purple,
textTheme: TextTheme(
headline: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
// backgroundColor: Colors.purple,
// color: Colors.white
),
subhead: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
body1: TextStyle(
fontSize: 14.0,
),
button: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
)),
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
HomePage();
final _HomePageState _homePageState = _HomePageState();
#override
_HomePageState createState() => _homePageState;
Future<void> setSelectedConnection() async {
await _homePageState.setSelectedConnection();
}
}
class _HomePageState extends State<HomePage> {
int _selectedConnectionId = -1;
bool _isSelectedConnectionReady = false;
Connection _selectedConnection;
int _selectedBottomNavigationIndex = 0;
List<Widget> _children = [];
#override
void initState() {
super.initState();
setSelectedConnection();
}
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(),
drawer: MyDrawer(),
body: myBody(),
bottomNavigationBar: myBottomNavigation(),
);
}
Future<void> setSelectedConnection() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int selectedConnectionId;
try {
selectedConnectionId = prefs.getInt("selectedConnectionId");
} catch (e) {
selectedConnectionId = -1;
_isSelectedConnectionReady = true;
}
List<Connection> connectionList =
await ConnectionDBProvider.db.getAllConnections();
if (selectedConnectionId is int &&
selectedConnectionId >= 0 &&
connectionList.isNotEmpty) {
if (connectionList[selectedConnectionId] is Connection) {
setState(() {
_selectedConnectionId = selectedConnectionId;
_selectedConnection = connectionList[_selectedConnectionId];
_isSelectedConnectionReady = true;
_children = [
ReportsPage(
baseurl: _selectedConnection.baseurl,
username: _selectedConnection.username,
password: _selectedConnection.password,
),
OrdersListPage(
baseurl: _selectedConnection.baseurl,
username: _selectedConnection.username,
password: _selectedConnection.password,
),
ProductsListPage(
baseurl: _selectedConnection.baseurl,
username: _selectedConnection.username,
password: _selectedConnection.password,
),
CustomersListPage(
baseurl: _selectedConnection.baseurl,
username: _selectedConnection.username,
password: _selectedConnection.password,
)
];
});
}
}
}
Widget myAppBar() {
Widget myAppBarWidgetData;
if (!_isSelectedConnectionReady &&
_selectedConnectionId is int &&
_selectedConnection is! Connection) {
myAppBarWidgetData = AppBar(
title: Text("Setup"),
);
}
return myAppBarWidgetData;
}
Widget myBody() {
Widget myBodyWidgetData = SizedBox.shrink();
if (_isSelectedConnectionReady &&
_selectedConnectionId is int &&
_selectedConnectionId >= 0 &&
_selectedConnection.runtimeType == Connection) {
myBodyWidgetData = IndexedStack(
index: _selectedBottomNavigationIndex,
children: _children,
);
} else if (_isSelectedConnectionReady && _selectedConnectionId < 0) {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
myBodyWidgetData = RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: setSelectedConnection,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(10, 20, 10, 20),
child: Text(
"Kindly add a connection to manage woocommerce",
textAlign: TextAlign.center,
),
),
Container(
height: 45,
width: 200,
child: RaisedButton(
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddConnectionPage(
refreshConnectionsList: setSelectedConnection,
),
),
);
},
child: Text(
"Add Connection",
style: Theme.of(context).textTheme.button,
),
),
)
],
),
),
);
} else {
myBodyWidgetData = Center(
child: SpinKitPulse(
color: Theme.of(context).primaryColor,
size: 70,
),
);
}
return myBodyWidgetData;
}
Widget myBottomNavigation() {
Widget myBottomNavigationWidgetData = SizedBox.shrink();
if (_isSelectedConnectionReady &&
_selectedBottomNavigationIndex >= 0 &&
_selectedConnection.runtimeType == Connection) {
myBottomNavigationWidgetData = BottomNavigationBar(
// backgroundColor: Colors.purple, //Not Working Don't Know why
showSelectedLabels: true,
showUnselectedLabels: true,
unselectedItemColor: Config.colors["lightTheme"]
["bottomNavInactiveColor"],
selectedItemColor: Config.colors["lightTheme"]["mainThemeColor"],
currentIndex: _selectedBottomNavigationIndex,
onTap: (int index) {
setState(() {
_selectedBottomNavigationIndex = index;
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
title: Text('Reports'),
),
BottomNavigationBarItem(
icon: Icon(Icons.assignment),
title: Text('Orders'),
),
BottomNavigationBarItem(
icon: Icon(Icons.collections),
title: Text('Products'),
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text('Customers'),
),
]);
}
return myBottomNavigationWidgetData;
}
}

I had the same issue, fixed with passing and assigning the GlobalKey() to the child.
Example:
setState(() {
_children.removeAt(0);
_children.insert(0, CustomWidget(customData: customData, key: GlobalKey()));
});

Related

How to change Elevated button's title and color onpressed

I'm developing an app to make meal reservations, and I have an ElevatedButton that opens an alert when pressed. This alert is where the user is able to confirm the reservation. So, the alert has 2 text buttons, and I need that when the "sim" button is pressed, the ElevatedButton changes from "Reservar" with green color to "Cancelar Reserva" with red color.
I tried this way but it doesn't work:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../components/meal_item.dart';
import '../models/day_of_week.dart';
import '../models/want_to_comment.dart';
import '../models/meal.dart';
import '../utils/app_routes.dart';
import '../data/dummy_data.dart';
enum StatusReserva { y, n }
Color statusToColor(StatusReserva value) {
Color color = Colors.green;
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
color = Colors.red;
break;
}
return color;
}
String statusToString(StatusReserva value) {
String title = 'Reservar';
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
title = 'Cancelar Reserva';
break;
}
return title;
}
class DaysOfWeekMealsScreen extends StatefulWidget {
final List<Meal> meals;
final StatusReserva status;
final Function(StatusReserva) onStatusChanged;
const DaysOfWeekMealsScreen({
Key? key,
required this.meals,
required this.status,
required this.onStatusChanged,
}) : super(key: key);
#override
State<DaysOfWeekMealsScreen> createState() => _DaysOfWeekMealsScreenState();
}
class _DaysOfWeekMealsScreenState extends State<DaysOfWeekMealsScreen> {
StatusReserva status = StatusReserva();
#override
void initState() {
super.initState();
status = widget.status;
}
#override
Widget build(BuildContext context) {
final dayOfWeek = ModalRoute.of(context)!.settings.arguments as DayOfWeek;
final dayOfWeekMeals = widget.meals.where((meal) {
return meal.days_of_week.contains(dayOfWeek.id);
}).toList();
void _incrementCount(BuildContext context) {
dayOfWeek.count++;
}
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
' ',
),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'Confirmar reserva para o dia XX/XX/XX?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold),
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: const Text('Sim'),
onPressed: () => {
_incrementCount,
Navigator.pop(context),
status = StatusReserva.y
},
),
TextButton(
child: const Text('Não'),
onPressed: () => Navigator.pop(context),
),
],
),
],
);
},
);
}
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
'assets/images/logo.png',
fit: BoxFit.contain,
height: 32,
),
Container(
padding: const EdgeInsets.all(8.0),
child: Text(dayOfWeek.title)),
],
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () => {_showMyDialog(context)},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
padding: MaterialStateProperty.all(const EdgeInsets.all(15)),
backgroundColor:
MaterialStateProperty.all(statusToColor(widget.status)),
),
child: Text(statusToString(widget.status)),
),
),
Expanded(
child: ListView.builder(
itemCount: dayOfWeekMeals.length,
itemBuilder: (ctx, index) {
return MealItem(dayOfWeekMeals[index]);
},
),
),
],
),
);
}
}
import 'package:apetit_project/models/want_to_comment.dart';
import 'package:apetit_project/screens/login_screen.dart';
import 'package:flutter/material.dart';
import 'screens/tabs_screen.dart';
import 'screens/days_of_week_meals_screen.dart';
import 'screens/meal_detail_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/want_to_comment_screen.dart';
import 'screens/comment_screen.dart';
import 'utils/app_routes.dart';
import 'models/meal.dart';
import 'models/settings.dart';
import 'data/dummy_data.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Settings settings = Settings();
StatusReserva status = StatusReserva();
List<Meal> _gourmetMeals = [];
List<Meal> _lightMeals = [];
void _filterMeals(Settings settings) {
setState(() {
this.settings = settings;
});
}
void _reserveMeals(StatusReserva status) {
setState(() {
this.status = status;
});
}
void _toggleGourmet(Meal meal) {
setState(() {
_gourmetMeals.contains(meal)
? _gourmetMeals.remove(meal)
: _gourmetMeals.add(meal);
});
}
bool _isGourmet(Meal meal) {
return _gourmetMeals.contains(meal);
}
void _toggleLight(Meal meal) {
setState(() {
_lightMeals.contains(meal)
? _lightMeals.remove(meal)
: _lightMeals.add(meal);
});
}
bool _isLight(Meal meal) {
return _lightMeals.contains(meal);
}
#override
Widget build(BuildContext context) {
final ThemeData tema = ThemeData(
fontFamily: 'Raleway',
textTheme: ThemeData.light().textTheme.copyWith(
headline6: const TextStyle(
fontSize: 20,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold,
),
),
);
return MaterialApp(
title: 'Appetit',
theme: tema.copyWith(
colorScheme: tema.colorScheme.copyWith(
primary: const Color.fromRGBO(222, 1, 59, 1),
secondary: const Color.fromRGBO(240, 222, 77, 1),
),
),
routes: {
AppRoutes.LOGIN: (ctx) => LoginScreen(),
AppRoutes.HOME: (ctx) => TabsScreen(_gourmetMeals, _lightMeals),
AppRoutes.WANT_TO_COMMENT: (ctx) => const WantToCommentScreen(),
AppRoutes.COMMENT: (ctx) => const CommentScreen(),
AppRoutes.DAYS_OF_WEEK_MEALS: (ctx) => DaysOfWeekMealsScreen(
meals: DUMMY_MEALS,
status,
_reserveMeals,
),
AppRoutes.MEAL_DETAIL: (ctx) => MealDetailScreen(
_toggleGourmet, _isGourmet, _toggleLight, _isLight),
AppRoutes.SETTINGS: (ctx) => SettingsScreen(settings, _filterMeals),
},
);
}
createMaterialColor(Color color) {}
}
To change the state of a widget you can use setState
enum StatusReserva { y, n }
Color statusToColor(StatusReserva value) {
Color color = Colors.green;
switch (value) {
case StatusReserva.n:
break;
case StatusReserva.y:
color = Colors.red;
break;
}
return color;
}
class StatusTest extends StatefulWidget {
const StatusTest({Key? key}) : super(key: key);
#override
State<StatusTest> createState() => _StatusTestState();
}
class _StatusTestState extends State<StatusTest> {
var status = StatusReserva.n;
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
' ',
),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'Confirmar reserva para o dia XX/XX/XX?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontFamily: 'Raleway',
fontWeight: FontWeight.bold),
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: const Text('Sim'),
onPressed: () => setState(() {
Navigator.pop(context);
status = StatusReserva.n;
}),
),
TextButton(
child: const Text('Não'),
onPressed: () => setState(() {
Navigator.pop(context);
status = StatusReserva.y;
}),
),
],
),
],
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 100,
height: 100,
child: Container(
color: statusToColor(status),
child: const Center(
child: Text(
'Status color',
style: TextStyle(color: Colors.white),
)),
),
),
ElevatedButton(
onPressed: () {
_showMyDialog(context);
},
child: const Text('Show dialog'),
),
],
),
),
);
}
}
For further clarification
The StatusReserva status = StatusReserva.n; should be in the State class _DaysOfWeekMealsScreenState. And to change it use setState. This value should never change if not inside a setState.
The Sim button onPressed should be like the following (Also with fixes of an unintended Set creation):
TextButton(
child: const Text('Sim'),
onPressed: () {
_incrementCount();
Navigator.pop(context);
setState(() => status = StatusReserva.y);
},
),
Use a boolean value instead of enum like this
bool isCancelReservar = false;
and in the state class before build method add this code :
bool isCancelReservar = false;
#override
void initState(){
isCancelReservar = widget.isCancelReservar;
super.initState();
}
Now you are able to update the value of isCancelReservar on clicking "sim button" using setState method. Your text button should look like this :
TextButton(
child: const Text('Sim'),
onPressed: () {
setState(() {
isCancelReservar = true;
});
_incrementCount,
Navigator.pop(context);
},
),
and your elevated button like this :
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
margin: const EdgeInsets.all(10),
child: ElevatedButton(
onPressed: () => {_showMyDialog(context)},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
padding: MaterialStateProperty.all(const EdgeInsets.all(15)),
backgroundColor:
MaterialStateProperty.all(isCancelReservar ? Colors.red :
Colors.green),
),
child: Text(isCancelReservar ? "Cancelar Reserva" : "Reservar"),
),
),
where you can see the backgroundColor will be red with text "Cancelar Reserva" if the value of isCancelReservar is true else it would be of color green with test Reservar.

how to store tasks in temporary storage in flutter (use Shared preferences or something similar)

I created a program to create tasks, everything works as it should, I made such options as validation, deleting the task, changing the theme. But the problem is that when I restart the app all the tasks are deleted. I want to keep them in storage, temporarily or permanently. And I can't realize it. Someone may have experience as such to implement ?? or examples of similar problems. My goal is that after restarting the application, all tasks remain.
My code
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main () {
runApp(MaterialApp(
home: App(),
));
}
class ListItem{
String todoText;
bool todoCheck;
ListItem(this.todoText, this.todoCheck);
}
class _strikeThrough extends StatelessWidget{
final String todoText;
final bool todoCheck;
_strikeThrough(this.todoText, this.todoCheck) : super();
Widget _widget(){
if(todoCheck){
return Text(
todoText,
style: TextStyle(
fontSize: 22.0,
),
);
}
else{
return Text(
todoText,
style: TextStyle(
fontSize: 22.0
),
);
}
}
#override
Widget build(BuildContext context){
return _widget();
}
}
class App extends StatefulWidget{
#override
AppState createState(){
return AppState();
}
}
final ValueNotifier<ThemeMode> _notifier = ValueNotifier(ThemeMode.light);
class AppState extends State<App> {
bool valText = true;
var counter = 0;
var IconsType = Icons.wb_sunny ;
late Color ColorType = Colors.black;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
List<ListItem> WidgetList = [];
#override
void dispose() {
textController.dispose();
popUpTextController.dispose();
super.dispose();
}
#override
void initState() {
addToSP(defaultList).then((_) => getSP());
super.initState();
}
Future<void> addToSP(List<List<ListItem>> tList) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('graphLists', jsonEncode(tList));
}
void getSP() async {
final prefs = await SharedPreferences.getInstance();
final List<dynamic> jsonData =
jsonDecode(prefs.getString('graphLists') ?? '[]');
WidgetList = jsonData.map<List<ListItem>>((jsonList) {
return jsonList.map<TodoInfo>((jsonItem) {
return ListItem.fromJson(jsonItem);
}).toList();
}).toList();
setState(() {});
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<ThemeMode>(
valueListenable: _notifier,
builder: (_, mode, __) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: mode, // Decides which theme to show, light or dark.
home: Scaffold(
appBar: AppBar(
title: Text("Список задач"),
actions: <Widget>[
IconButton(
icon: Icon(IconsType,color : ColorType
),
onPressed:() =>
{
if (_notifier.value == ThemeMode.light) {
_notifier.value = ThemeMode.dark,
IconsType = Icons.dark_mode,
ColorType = Colors.white,
} else
{
_notifier.value = ThemeMode.light,
IconsType = Icons.wb_sunny,
ColorType = Colors.black,
}
}
)
],
//backgroundColor: Colors.orange[500],
iconTheme: IconThemeData(
color: Colors.white
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
"Tasks",
style: TextStyle(
fontSize: 70.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
IconButton(
color: Colors.black,
iconSize: 70,
constraints: const BoxConstraints(),
padding: EdgeInsets.fromLTRB(30.0, 10.0, 30, 10.0),
icon: const Icon(Icons.add_outlined),
onPressed: () {
if (textController.text.replaceAll(" ", "").isNotEmpty) {
WidgetList.insert(0, new ListItem(textController.text.replaceAll(" ", ""), false));
setState(() {
valText = true;
textController.clear();
});
}
else
{
setState(() {
valText = false;
});
}
},
)
],
),
),
Container(
width: MediaQuery
.of(context)
.size
.height * 0.45,
child: TextField(
style: TextStyle(
fontSize: 22.0,
//color: Theme.of(context).accentColor,
),
controller: textController,
cursorWidth: 5.0,
autocorrect: true,
autofocus: true,
//onSubmitted: ,
),
),
Align(
child:
(valText == false) ?
Align(child: Text(("Задача пустая"),
style: TextStyle(
fontSize: 25.0, color: Colors.red)),
alignment: Alignment.center) :
Align(child: Text((""),),
alignment: Alignment.center)),
Expanded(
child: ReorderableListView(
children: <Widget>[
for(final widget in WidgetList)
GestureDetector(
key: Key(widget.todoText),
child: Dismissible(
key: Key(widget.todoText),
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
//key: ValueKey("Checkboxtile $widget"),
value: widget.todoCheck,
title: _strikeThrough(
widget.todoText, widget.todoCheck),
onChanged: (checkValue) {
//_strikethrough toggle
setState(() {
if (!checkValue!) {
widget.todoCheck = false;
}
else {
widget.todoCheck = true;
}
});
},
),
background: Container(
child: Icon(Icons.delete),
alignment: Alignment.centerRight,
color: Colors.redAccent,
),
direction: DismissDirection.endToStart,
movementDuration: const Duration(
milliseconds: 200),
onDismissed: (dismissDirection) { //Delete Todo
WidgetList.remove(widget);
},
),
)
],
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
var replaceWiget = WidgetList.removeAt(oldIndex);
WidgetList.insert(newIndex, replaceWiget);
});
},
),
)
],
),
)
);
}
);
}
}
class TodoInfo {
String todoText;
bool todoCheck;
TodoInfo({
required this.todoText,
required this.todoCheck,
});
factory TodoInfo.fromJson(Map<String, dynamic> json) {
return TodoInfo(
todoText: json["todoText"],
todoCheck: json["todoCheck"]);
}
factory TodoInfo.fromMap(Map<String, dynamic> map) => TodoInfo(
todoText: map["todoText"]?? '',
todoCheck: map["todoCheck"] ?? '',
);
Map<String, dynamic> toJson() {
return {
"todoText": todoText,
"todoCheck": todoCheck
};
}
#override
String toString() => '{todoText: $todoText, todoCheck: $todoCheck}';
}
My skrin
Here are some options for persistent memory.
You could use any of the storage functions/plugins available for flutter as per your need or interest.
Some of them are
Seflite
Shared preferences
Hive
Example on how to use Shared Preferences: LINK

Sort tasks in the list in the flutter

I am developing a task book, most of the tasks I have implemented but there is one that I can't solve, when I add a task it is added to the end of the list, and I want it to be at the top of the list. And also there is a problem when the theme changes, I want that if the theme is black, the text "Tasks +" were white and vice versa. Here is my code:
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
home: App(),
));
}
class ListItem{
String todoText;
bool todoCheck;
ListItem(this.todoText, this.todoCheck);
}
class _strikeThrough extends StatelessWidget{
final String todoText;
final bool todoCheck;
_strikeThrough(this.todoText, this.todoCheck) : super();
Widget _widget(){
if(todoCheck){
return Text(
todoText,
style: TextStyle(
fontSize: 22.0,
),
);
}
else{
return Text(
todoText,
style: TextStyle(
fontSize: 22.0
),
);
}
}
#override
Widget build(BuildContext context){
return _widget();
}
}
const Color ColorTextW = Colors.black;
class App extends StatefulWidget{
#override
AppState createState(){
return AppState();
}
}
final ValueNotifier<ThemeMode> _notifier = ValueNotifier(ThemeMode.light);
late Color ColorType = Colors.black;
class AppState extends State<App> {
bool valText = true;
var counter = 0;
var IconsType = Icons.wb_sunny ;
late Color ColorType = Colors.black;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
List<ListItem> WidgetList = [];
#override
void dispose() {
textController.dispose();
popUpTextController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<ThemeMode>(
valueListenable: _notifier,
builder: (_, mode, __) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: mode, // Decides which theme to show, light or dark.
home: Scaffold(
appBar: AppBar(
title: Text("Список задач"),
actions: <Widget>[
IconButton(
icon: Icon(IconsType,color : ColorType
),
onPressed:() =>
{
if (_notifier.value == ThemeMode.light) {
_notifier.value = ThemeMode.dark,
IconsType = Icons.dark_mode,
ColorType = Colors.white,
} else
{
_notifier.value = ThemeMode.light,
IconsType = Icons.wb_sunny,
ColorType = Colors.black,
}
}
)
],
//backgroundColor: Colors.orange[500],
iconTheme: IconThemeData(
color: Colors.white
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
"Tasks",
style: TextStyle(
fontSize: 70.0,
fontWeight: FontWeight.bold,
color: ColorTextW,
),
),
IconButton(
color: Colors.black,
iconSize: 70,
constraints: const BoxConstraints(),
padding: EdgeInsets.fromLTRB(30.0, 10.0, 30, 10.0),
icon: const Icon(Icons.add_outlined),
onPressed: () {
if (textController.text.replaceAll(" ", "").isNotEmpty) {
WidgetList.add(
new ListItem(textController.text.replaceAll(" ", ""), false));
setState(() {
valText = true;
textController.clear();
});
}
else
{
setState(() {
valText = false;
});
}
},
)
],
),
),
Container(
width: MediaQuery
.of(context)
.size
.height * 0.45,
child: TextField(
style: TextStyle(
fontSize: 22.0,
//color: Theme.of(context).accentColor,
),
controller: textController,
cursorWidth: 5.0,
autocorrect: true,
autofocus: true,
//onSubmitted: ,
),
),
Align(
child:
(valText == false) ?
Align(child: Text(("Задача пустая"),
style: TextStyle(
fontSize: 25.0, color: Colors.red)),
alignment: Alignment.center) :
Align(child: Text((""),),
alignment: Alignment.center)),
Expanded(
child: ReorderableListView(
children: <Widget>[
for(final widget in WidgetList)
GestureDetector(
key: Key(widget.todoText),
child: Dismissible(
key: Key(widget.todoText),
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
//key: ValueKey("Checkboxtile $widget"),
value: widget.todoCheck,
title: _strikeThrough(
widget.todoText, widget.todoCheck),
onChanged: (checkValue) {
//_strikethrough toggle
setState(() {
if (!checkValue!) {
widget.todoCheck = false;
}
else {
widget.todoCheck = true;
}
});
},
),
background: Container(
child: Icon(Icons.delete),
alignment: Alignment.centerRight,
color: Colors.redAccent,
),
direction: DismissDirection.endToStart,
movementDuration: const Duration(
milliseconds: 200),
onDismissed: (dismissDirection) { //Delete Todo
WidgetList.remove(widget);
},
),
)
],
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
var replaceWiget = WidgetList.removeAt(oldIndex);
WidgetList.insert(newIndex, replaceWiget);
});
},
),
)
],
),
)
);
}
);
}
}
In the onPressed method of your Add-IconButton do the following:
WidgetList.insert(0, new ListItem(textController.text.replaceAll(" ", ""), false));
This will insert the new item at the top of the existing list ^^

how to convert latitude and longtitude to a current address in flutter, get time in and time out and time duration and store in firebase

Im a flutter and noob, and having problems trying too convert latitude and longitude to a current address in flutter using geocoding and store in firebase/firestore in an app im trying to develop. The App stores latitude and longitude in firestore once the user .logs in and enables live loocation, the current latitude and longitude are displayed but the current address is yet to be displayed.
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:demo/model/user_model.dart';
import 'package:demo/screens/liveMap.dart';
import 'package:geocoding/geocoding.dart';
import 'package:demo/screens/login_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:location/location.dart' as loc;
import 'package:permission_handler/permission_handler.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
final loc.Location location = loc.Location();
StreamSubscription<loc.LocationData>? _locationSubscription;
#override
void initState() {
super.initState();
FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then((value) {
this.loggedInUser = UserModel.fromMap(value.data());
setState(() {});
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Welcome'),
centerTitle: true,
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 130,
child: Image.asset(
'assets/images/logo.png',
fit: BoxFit.contain,
),
),
TextButton(
onPressed: () {
_getLocation();
},
child: Text('Enable Live Location'),
),
TextButton(
onPressed: () {
_stopListening();
},
child: Text('Stop Live Location'),
),
Expanded(
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('location')
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
snapshot.data!.docs[index]['name'].toString()),
subtitle: Row(
children: [
Text(snapshot.data!.docs[index]['latitude']
.toString()),
SizedBox(width: 20),
Text(snapshot.data!.docs[index]['longitude']
.toString()),
],
),
trailing: IconButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
LiveMap(snapshot.data!.docs[index].id)));
},
icon: Icon(Icons.directions),
),
);
});
},
)),
SizedBox(
height: 10,
),
Text(
'${loggedInUser.name}',
style: TextStyle(color: Colors.black54),
),
Text(
'${loggedInUser.email}',
style: TextStyle(color: Colors.black54),
),
SizedBox(
height: 15,
),
ActionChip(
label: Text('Logout'),
onPressed: () {
logout(context);
})
],
),
),
),
);
}
Future<void> logout(BuildContext context) async {
await FirebaseAuth.instance.signOut();
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginScreen()));
}
Future<void> _getLocation() async {
_locationSubscription = location.onLocationChanged.handleError((onError) {
print(onError);
_locationSubscription?.cancel();
setState(() {
_locationSubscription = null;
});
}).listen((loc.LocationData currentLocation) async {
await FirebaseFirestore.instance.collection('location').doc('user1').set({
'latitude': currentLocation.latitude,
'longitude': currentLocation.longitude,
'name': '${loggedInUser.name}',
''
}, SetOptions(merge: true));
});
}
_stopListening() {
_locationSubscription?.cancel();
setState(() {
_locationSubscription = null;
});
}
To get the address from latitude and longitude in flutter:
1.add geocodeing to pubspec.yaml using:
flutter pub add geocoding
import
import 'package:geocoding/geocoding.dart';
use such function
String _address = "";
print_Address() async {
List<Placemark> newPlace = await placemarkFromCoordinates(51.3468345, 6.8754743);
Placemark placeMark = newPlace[0];
String? name = placeMark.name;
String? subLocality = placeMark.subLocality;
String? locality = placeMark.locality;
String? administrativeArea = placeMark.administrativeArea;
String? postalCode = placeMark.postalCode;
String? country = placeMark.country;
String address = "${name}, ${subLocality}, ${locality}, ${administrativeArea} ${postalCode}, ${country}";
print(address); // do what you want with it
_address = address;
}
dependencies:
geocoding: ^2.0.1
Example:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'template/globals.dart';
void main() {
runApp(BaseflowPluginExample());
}
/// A Flutter application demonstrating the functionality of this plugin
class BaseflowPluginExample extends StatelessWidget {
/// [MaterialColor] to be used in the app [ThemeData]
final MaterialColor themeMaterialColor =
createMaterialColor(const Color.fromRGBO(48, 49, 60, 1));
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Baseflow $pluginName',
theme: ThemeData(
accentColor: Colors.white60,
backgroundColor: const Color.fromRGBO(48, 49, 60, 0.8),
buttonTheme: ButtonThemeData(
buttonColor: themeMaterialColor.shade500,
disabledColor: themeMaterialColor.withRed(200),
splashColor: themeMaterialColor.shade50,
textTheme: ButtonTextTheme.primary,
),
bottomAppBarColor: const Color.fromRGBO(57, 58, 71, 1),
hintColor: themeMaterialColor.shade500,
primarySwatch: createMaterialColor(const Color.fromRGBO(48, 49, 60, 1)),
textTheme: TextTheme(
bodyText1: TextStyle(
color: Colors.white,
fontSize: 16,
height: 1.3,
),
bodyText2: TextStyle(
color: Colors.white,
fontSize: 18,
height: 1.2,
),
button: TextStyle(color: Colors.white),
headline1: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
visualDensity: VisualDensity.adaptivePlatformDensity,
inputDecorationTheme: InputDecorationTheme(
fillColor: const Color.fromRGBO(37, 37, 37, 1),
filled: true,
),
),
home: AppHome(title: 'Baseflow $pluginName example app'),
);
}
/// Creates a [MaterialColor] based on the supplied [Color]
static MaterialColor createMaterialColor(Color color) {
final strengths = <double>[.05];
final swatch = <int, Color>{};
final r = color.red, g = color.green, b = color.blue;
for (var i = 1; i < 10; i++) {
strengths.add(0.1 * i);
}
for (var strength in strengths) {
final ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
return MaterialColor(color.value, swatch);
}
}
/// A Flutter example demonstrating how the [pluginName] plugin could be used
class AppHome extends StatefulWidget {
/// Constructs the [AppHome] class
AppHome({required this.title});
/// The [title] of the application, which is shown in the application's
/// title bar.
final String title;
#override
_AppHomeState createState() => _AppHomeState();
}
class _AppHomeState extends State<AppHome> {
final _pageController = PageController(initialPage: 0);
var _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).bottomAppBarColor,
title: Center(
child: Image.asset(
'res/images/baseflow_logo_def_light-02.png',
width: 140,
),
),
),
backgroundColor: Theme.of(context).backgroundColor,
body: PageView(
controller: _pageController,
children: pages,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
},
),
bottomNavigationBar: _bottomAppBar(),
);
}
BottomAppBar _bottomAppBar() {
return BottomAppBar(
elevation: 5,
color: Theme.of(context).bottomAppBarColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.unmodifiable(() sync* {
for (var i = 0; i < pages.length; i++) {
yield Expanded(
child: IconButton(
iconSize: 30,
icon: Icon(icons.elementAt(i)),
color: _bottomAppBarIconColor(i),
onPressed: () => _animateToPage(i),
),
);
}
}()),
),
);
}
void _animateToPage(int page) {
_pageController.animateToPage(page,
duration: Duration(milliseconds: 200), curve: Curves.linear);
}
Color _bottomAppBarIconColor(int page) {
return _currentPage == page ? Colors.white : Theme.of(context).accentColor;
}
}

Flutter paginated data table

I'm using PaginatedDataTable widget to show data from api. It works perfectly, so I have set rowsPerPage to 4 and if my data length is for example 7, I get in my first page 4 rows with data and in the second one I get tree others and one empty row. That's the problem, on the second page, I want get only rows that contains data and no empty row. Guess that everyone understands what I mean.
Thank you in advance.
my code =>
class DashboardScreen extends StatefulWidget {
DashboardScreen(
{this.token,
this.f_name,
this.phone,
this.l_name,
this.type_client,
this.email,
this.addresse,
this.num_client});
String token;
final String f_name;
final String l_name;
final String email;
final String addresse;
final String num_client;
final String phone;
final String type_client;
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen>
with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
AnimationController _animationController;
Animation<double> _animateIcon;
bool isOpened = false;
String token;
String f_name;
String l_name;
String email;
String addresse;
String num_client;
String phone, type_client;
bool showSpinner = false;
List<Transaction> transactions = [];
int rowsPerPAge = 4;
Future<List<Transaction>> _getTransactionsData() async {
setState(() {
showSpinner = true;
});
GetToken getToken = GetToken(token: "${widget.token}");
var freshToken = await getToken.getTokenData();
try {
Map<String, String> newHeader = {'Authorization': 'Bearer $freshToken'};
GetUserData getUserData = GetUserData(
'${APIConstants.API_BASE_URL}/...', newHeader);
var userData = await getUserData.getData();
//print(userData);
var apiError = userData['error'];
var apiCode = userData['code'];
try {
if (apiError == false && apiCode == 200) {
final items = userData['data'].cast<Map<String, dynamic>>();
List<Transaction> listOfTransactions = items.map<Transaction> ((json) {
return Transaction.fromJson(json);
}).toList();
setState(() {
showSpinner = false;
});
//print("succes: $userData");
return listOfTransactions;
}
else if(apiCode == 401){
setState(() {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
backgroundColor: MyColor.hintColor,
elevation: 10,
content: new Text(SnackBarText.TIME_OUT, style: GoogleFonts.roboto(color: Colors.white, fontSize: 20)),
));
showSpinner = false;
return MaterialPageRoute(builder: (context) => LoginScreen());
});
}
else {
//print("erreur: $userData");
setState(() {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
backgroundColor: MyColor.hintColor,
elevation: 10,
content: new Text(SnackBarText.SERVER_ERROR,
style: GoogleFonts.roboto(color: Colors.white, fontSize: 20)),
));
return showSpinner = false;
});
}
} catch (e) {
print(e);
}
} catch (e) {
print(e);
print('1');
}
}
#override
void initState() {
super.initState();
token = widget.token;
f_name = widget.f_name;
l_name = widget.l_name;
email = widget.email;
addresse = widget.addresse;
num_client = widget.num_client;
phone = widget.phone;
type_client = widget.type_client;
_getTransactionsData().then((transactionsFromServer) {
transactions = transactionsFromServer;
//print(transactions);
});
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animateIcon =
Tween<double>(begin: 1.0, end: 2.0).animate(_animationController);
}
#override
Widget build(BuildContext context) {
//FactureProvider factureProvider = Provider.of<FactureProvider>(context);
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
var orientation = MediaQuery.of(context).orientation;
bool portrait = orientation == Orientation.portrait;
return WillPopScope(
child: Scaffold(
key: _scaffoldKey,
backgroundColor: MyColor.myBackgroundColor,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: false,
backgroundColor: Colors.white,
leading: new IconButton(
padding: EdgeInsets.all(0.0),
icon: new Icon(
Icons.apps,
color: MyColor.menuColor,
),
onPressed: () => _scaffoldKey.currentState.openDrawer()),
title: FittedBox(
fit: BoxFit.fitWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
//SizedBox(width: 30,),
//ImageIcon(AssetImage('images/notification.png'), color: Colors.black,),
Text(
Texts.DASHBOARD,
style: GoogleFonts.roboto(
color: MyColor.hintColor,
fontWeight: FontWeight.bold,
fontSize: 20),
textAlign: TextAlign.left,
),
SizedBox(
width: portrait ? width/4.0 : width/1.5,
),
GestureDetector(
onTap: _goToProfilScreen,
child: ImageIcon(
AssetImage('images/noun_avatar_1.png'),
color: Colors.black,
)),
],
),
),
),
drawer: buildDrawer,
body: Loader(
color: Colors.white.withOpacity(0.3),
loadIng: showSpinner,
child: ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Row(
children: <Widget>[
Conditioned(
cases:[
Case( (transactions?.length == 0 || transactions?.length == null), builder: () => Padding(
padding: const EdgeInsets.only(left: 10.0, top: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
Text(Texts.HISTO_TRANSAC, style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w700), textAlign: TextAlign.start,)
],
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: Text(Texts.NO_TRANSAC, style: GoogleFonts.roboto(color: MyColor.hintColor, fontSize: 15),),),
),
],
),
)),
],
defaultBuilder: () => Padding(
padding: const EdgeInsets.only(left: 1.0, right: 1.0),
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
width: MediaQuery.of(context).size.width/1.005,
child: PaginatedDataTable(
header: Text(Texts.HISTO_TRANSAC, style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w700), textAlign: TextAlign.start,),
rowsPerPage: transactions.length <= rowsPerPAge ? transactions.length : rowsPerPAge,
horizontalMargin: 3.7,
columnSpacing: 1.8,
headingRowHeight: 15,
dataRowHeight: 30,
columns: [
DataColumn(label: Text(Texts.dATE, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.mONTANT_PAYE, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.SERVICE_TEXT, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.mODE_PAIEMENT, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.DETAILS, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
],
source: DTS(transactions, context, abonnementsById)
),
),
),
),
),
],
),
),
)
],
),
),
bottomNavigationBar: GestureDetector(
onTap: () => showMaterialModalBottomSheet(
context: context,
useRootNavigator: true,
bounce: true,
//secondAnimation: AnimationController.unbounded(vsync: this, duration: Duration(seconds: 30)),
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (context, scrollController) => buildWrap(context),
),
child: Container(
color: MyColor.bottonNavColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
Texts.NEW,
style: GoogleFonts.roboto(
color: MyColor.hintColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
SizedBox(
height: 50,
width: 50,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
clipBehavior: Clip.hardEdge,
autofocus: true,
mini: true,
backgroundColor: MyColor.hintColor,
onPressed: () => showMaterialModalBottomSheet(
context: context,
useRootNavigator: true,
bounce: true,
//secondAnimation: AnimationController.unbounded(vsync: this, duration: Duration(seconds: 30)),
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
buildWrap(context),
),
child: AnimatedIcon(
icon: AnimatedIcons.event_add,
size: 30,
progress: _animateIcon,
),
),
),
)
],
),
),
),
),
onWillPop: _onWillPop,
);
}
}
You can add a checker on PaginatedDataTable's onPageChanged to check if the number of items to be displayed on the table is less than the default number of rows.
PaginatedDataTable(
rowsPerPage: _rowsPerPage,
source: RowSource(),
onPageChanged: (int? n) {
/// value of n is the number of rows displayed so far
setState(() {
if (n != null) {
debugPrint(
'onRowsPerPageChanged $_rowsPerPage ${RowSource()._rowCount - n}');
/// Update rowsPerPage if the remaining count is less than the default rowsPerPage
if (RowSource()._rowCount - n < _rowsPerPage)
_rowsPerPage = RowSource()._rowCount - n;
/// else, restore default rowsPerPage value
else _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
} else
_rowsPerPage = 0;
});
},
columns: <DataColumn>[],
)
Here's the complete sample.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#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> {
/// Set default number of rows to be displayed per page
var _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: PaginatedDataTable(
rowsPerPage: _rowsPerPage,
source: RowSource(),
onPageChanged: (int? n) {
/// value of n is the number of rows displayed so far
setState(() {
if (n != null) {
debugPrint(
'onRowsPerPageChanged $_rowsPerPage ${RowSource()._rowCount - n}');
/// Update rowsPerPage if the remaining count is less than the default rowsPerPage
if (RowSource()._rowCount - n < _rowsPerPage)
_rowsPerPage = RowSource()._rowCount - n;
/// else, restore default rowsPerPage value
else _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
} else
_rowsPerPage = 0;
});
},
columns: [
DataColumn(
label: Text(
'Foo',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Bar',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
],
),
),
);
}
}
class RowSource extends DataTableSource {
final _rowCount = 26;
#override
DataRow? getRow(int index) {
if (index < _rowCount) {
return DataRow(cells: <DataCell>[
DataCell(Text('Foo $index')),
DataCell(Text('Bar $index'))
]);
} else
return null;
}
#override
bool get isRowCountApproximate => true;
#override
int get rowCount => _rowCount;
#override
int get selectedRowCount => 0;
}