Closed sashkent3 closed 6 months ago
I don't know. If you provide a minimal repro case I could look at it
Hi @maxim-saplin. Here's a small example. Observe how if you toggle the theme colored rows do not change their color until the table is refreshed (because we need to call getRows
to get the updated colors).
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode themeMode = ThemeMode.light;
void toggleThemeMode() {
setState(() {
themeMode =
themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
});
}
ThemeData lightTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
brightness: Brightness.light,
);
ThemeData darkTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
brightness: Brightness.dark,
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: MyHomePage(
title: 'Flutter Demo Home Page',
toggleThemeMode: toggleThemeMode,
darkSurfaceVariant: darkTheme.colorScheme.surfaceVariant,
lightSurfaceVariant: lightTheme.colorScheme.surfaceVariant,
),
);
}
}
class NastySource extends AsyncDataTableSource {
NastySource({
required this.darkSurfaceVariant,
required this.lightSurfaceVariant,
});
final offlineRows = List.generate(100, (i) => (i * 3, i * 3 + 1, i * 3 + 2));
bool themeIsDark = false;
final Color darkSurfaceVariant;
final Color lightSurfaceVariant;
@override
Future<AsyncRowsResponse> getRows(int startIndex, int count) {
return Future.delayed(
const Duration(seconds: 3),
() => AsyncRowsResponse(
offlineRows.length,
offlineRows
.getRange(startIndex, startIndex + count)
.map(
(e) => DataRow2(
cells: [
DataCell(Text(e.$1.toString())),
DataCell(Text(e.$2.toString())),
DataCell(Text(e.$3.toString()))
],
color: e.$2 % 4 == 0
? MaterialStatePropertyAll(
themeIsDark ? darkSurfaceVariant : lightSurfaceVariant,
)
: null,
),
)
.toList(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
required this.title,
required this.toggleThemeMode,
required this.darkSurfaceVariant,
required this.lightSurfaceVariant,
});
final String title;
final void Function() toggleThemeMode;
final Color darkSurfaceVariant;
final Color lightSurfaceVariant;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final NastySource source;
@override
void initState() {
super.initState();
source = NastySource(
darkSurfaceVariant: widget.darkSurfaceVariant,
lightSurfaceVariant: widget.lightSurfaceVariant,
);
}
@override
Widget build(BuildContext context) {
source.themeIsDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: AsyncPaginatedDataTable2(
columns: const [
DataColumn2(label: Text('One')),
DataColumn2(label: Text('Two')),
DataColumn2(label: Text('Three')),
],
source: source,
autoRowsToHeight: true,
header: const Text('Nasty DataTable2'),
actions: [
IconButton(
onPressed: () => source.refreshDatasource(),
icon: const Icon(Icons.refresh))
],
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: widget.toggleThemeMode,
label: const Text('TOGGLE THEME'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
You can do this using resolve method of material property:
color: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (e.$2 % 4 == 0) {
return isDarkTheme
? darkSurfaceVariant
: lightSurfaceVariant;
}
return null; // Return null for rows that do not need specific styling.
}),
Here's a full example. It uses ugly global var for the theme status, you can do it properly in your code:
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/material.dart';
bool isDarkTheme = false;
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode themeMode = ThemeMode.light;
void toggleThemeMode() {
setState(() {
themeMode =
themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
isDarkTheme = themeMode == ThemeMode.dark;
});
}
ThemeData lightTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
brightness: Brightness.light,
);
ThemeData darkTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
brightness: Brightness.dark,
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: MyHomePage(
title: 'Flutter Demo Home Page',
toggleThemeMode: toggleThemeMode,
darkSurfaceVariant: darkTheme.colorScheme.surfaceVariant,
lightSurfaceVariant: lightTheme.colorScheme.surfaceVariant,
),
);
}
}
class NastySource extends AsyncDataTableSource {
NastySource({
required this.darkSurfaceVariant,
required this.lightSurfaceVariant,
});
final offlineRows = List.generate(100, (i) => (i * 3, i * 3 + 1, i * 3 + 2));
bool themeIsDark = false;
final Color darkSurfaceVariant;
final Color lightSurfaceVariant;
@override
Future<AsyncRowsResponse> getRows(int startIndex, int count) {
return Future.delayed(
const Duration(seconds: 3),
() => AsyncRowsResponse(
offlineRows.length,
offlineRows
.getRange(startIndex, startIndex + count)
.map(
(e) => DataRow2(
cells: [
DataCell(Text(e.$1.toString())),
DataCell(Text(e.$2.toString())),
DataCell(Text(e.$3.toString()))
],
color: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (e.$2 % 4 == 0) {
return isDarkTheme
? darkSurfaceVariant
: lightSurfaceVariant;
}
return null; // Return null for rows that do not need specific styling.
}),
// color: e.$2 % 4 == 0
// ? MaterialStatePropertyAll(
// themeIsDark ? darkSurfaceVariant : lightSurfaceVariant,
// )
// : null,
),
)
.toList(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
required this.title,
required this.toggleThemeMode,
required this.darkSurfaceVariant,
required this.lightSurfaceVariant,
});
final String title;
final void Function() toggleThemeMode;
final Color darkSurfaceVariant;
final Color lightSurfaceVariant;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final NastySource source;
@override
void initState() {
super.initState();
source = NastySource(
darkSurfaceVariant: widget.darkSurfaceVariant,
lightSurfaceVariant: widget.lightSurfaceVariant,
);
}
@override
Widget build(BuildContext context) {
source.themeIsDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: AsyncPaginatedDataTable2(
columns: const [
DataColumn2(label: Text('One')),
DataColumn2(label: Text('Two')),
DataColumn2(label: Text('Three')),
],
source: source,
autoRowsToHeight: true,
header: const Text('Nasty DataTable2'),
actions: [
IconButton(
onPressed: () => source.refreshDatasource(),
icon: const Icon(Icons.refresh))
],
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: widget.toggleThemeMode,
label: const Text('TOGGLE THEME'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
Clever solution! I will verify if it works and close the issue later. BTW, any way to acquire BuildContext
in .resolveWith()
? I would like to use Theme.of(context)
directly.
From the top of my head... You can add context param to data source constructor, create the data source on every build, pass in tte context into data source... In the getRows use the context from the field, also make sure the context is mounted: https://api.flutter.dev/flutter/widgets/BuildContext/mounted.html
Creating a new data source on every build doesn't seem to work for some reason, however this seems to be a good enough solution:
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode themeMode = ThemeMode.light;
void toggleThemeMode() {
setState(() {
themeMode =
themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
});
}
ThemeData lightTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
brightness: Brightness.light,
);
ThemeData darkTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
brightness: Brightness.dark,
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: MyHomePage(
title: 'Flutter Demo Home Page',
toggleThemeMode: toggleThemeMode,
),
);
}
}
class NastySource extends AsyncDataTableSource {
NastySource();
final offlineRows = List.generate(100, (i) => (i * 3, i * 3 + 1, i * 3 + 2));
late Color surfaceVariant;
@override
Future<AsyncRowsResponse> getRows(int startIndex, int count) {
return Future.delayed(
const Duration(seconds: 3),
() => AsyncRowsResponse(
offlineRows.length,
offlineRows
.getRange(startIndex, startIndex + count)
.map(
(e) => DataRow2(
cells: [
DataCell(Text(e.$1.toString())),
DataCell(Text(e.$2.toString())),
DataCell(Text(e.$3.toString()))
],
color: e.$2 % 4 == 0
? MaterialStateProperty.resolveWith((_) => surfaceVariant)
: null,
),
)
.toList(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
required this.title,
required this.toggleThemeMode,
});
final String title;
final void Function() toggleThemeMode;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final NastySource source;
@override
void initState() {
super.initState();
source = NastySource();
}
@override
Widget build(BuildContext context) {
source.surfaceVariant = Theme.of(context).colorScheme.surfaceVariant;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: AsyncPaginatedDataTable2(
columns: const [
DataColumn2(label: Text('One')),
DataColumn2(label: Text('Two')),
DataColumn2(label: Text('Three')),
],
source: source,
autoRowsToHeight: true,
header: const Text('Nasty DataTable2'),
actions: [
IconButton(
onPressed: () => source.refreshDatasource(),
icon: const Icon(Icons.refresh))
],
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: widget.toggleThemeMode,
label: const Text('TOGGLE THEME'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
Hi! My Flutter application has a theme brightness toggle and an
AsyncPaginatedDataTable2
. However, I have trouble updating the rows' colors. If the initial color of a DataRow2 isnull
then it reacts to theme brightness changes properly. However, if I customize their color depending on the row data, it updates the look only when the.getRows()
is called (for example via.refreshDatasource()
). For the whole duration of the loading animation custom colored rows look nasty, cause they don't match the theme's brightness. Is there any way to resolve this?