I am getting the error:
Unhandled Exception: LateInitializationError: Field '_deviceCalendarPlugin#332335403' has not been initialized.late DeviceCalendarPlugin _deviceCalendarPlugin
This statement getting error I am adding ? sign but in console the error is showing null string is not supporting.
I am creating doctor appointment event
class DoctorCalendarsPage extends StatefulWidget {
const DoctorCalendarsPage({Key? key, required String calendarName, required void Function() opendrawer, required double animationtime})
: super(key: key);
#override
_DoctorCalendarsPageState createState() {
return _DoctorCalendarsPageState();
}
}
class _DoctorCalendarsPageState extends State<DoctorCalendarsPage> {
late DeviceCalendarPlugin _deviceCalendarPlugin;
List<Calendar> _calendar = [];
List<Calendar> get _writableCalendars =>
_calendar.where((c) => c.isReadOnly == false).toList();
List<Calendar> get _readOnlyCalendars =>
_calendar.where((c) => c.isReadOnly == true).toList();
_CalendarsPageState() {
_deviceCalendarPlugin = DeviceCalendarPlugin();
}
#override
void initState() {
super.initState();
_retrieveCalendars();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
flex: 1,
child: ListView.builder(
itemCount: _calendar.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
key: Key(_calendar[index].isReadOnly == true
? 'readOnlyCalendar${_readOnlyCalendars.indexWhere((c) => c.id == _calendar[index].id)}'
: 'writableCalendar${_writableCalendars.indexWhere((c) => c.id == _calendar[index].id)}'),
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorEventsPage(_calendar[index],
key: const Key('calendarEventsPage'));
}));
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
height: 22,
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
_calendar[index].name!,
style: Theme.of(context).textTheme.subtitle1,
),
),
Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(_calendar[index].color!)),
),
const SizedBox(width: 10),
// Container(
// margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0),
// padding: const EdgeInsets.all(3.0),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blueAccent)),
// // child: const Text('Default'),
// ),
Icon(_calendar[index].isReadOnly == true
? Icons.lock
: Icons.lock_open)
],
),
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final createCalendar = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorCalendarAddPage();
}));
if (createCalendar == true) {
_retrieveCalendars();
}
},
child: const Icon(Icons.add),
backgroundColor: Colors.green,
),
);
}
void _retrieveCalendars() async {
try {
var permissionsGranted = await _deviceCalendarPlugin.hasPermissions();
if (permissionsGranted.isSuccess &&
(permissionsGranted.data == null ||
permissionsGranted.data == false)) {
permissionsGranted = await _deviceCalendarPlugin.requestPermissions();
if (!permissionsGranted.isSuccess ||
permissionsGranted.data == null ||
permissionsGranted.data == false) {
return;
}
}
final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars();
setState(() {
_calendar = calendarsResult.data as List<Calendar>;
});
} on PlatformException catch (e) {
if (kDebugMode) {
print(e);
}
}
}
}
Add _CalendarsPageState in your init state
#override
void initState() {
super.initState();
_retrieveCalendars();
_CalendarsPageState();
}
The following mean the variable will never be null.. so u have to assign it first before using it
late DeviceCalendarPlugin _deviceCalendarPlugin;
-----------------------------
You can use the following way.. so until u have get the data u want to assign the variable will be null
DeviceCalendarPlugin? _deviceCalendarPlugin;
Try initializing the instance first before calling it's function. Add _deviceCalendarPlugin = DeviceCalendarPlugin(); or _CalendarsPageState() prior to calling anything related to its function.
Related
Calendars from my device and local accounts are not displayed on my screen The calendar is correctly presented when I execute my code using USB, however when I generate an apk and then install it on my device, the calendar is not visible.i am using device calendar plugin i get this code from there account Can anyone assist in identifying the problem?
class CalendarsPage extends StatefulWidget {
const CalendarsPage(
{Key? key,
required String calendarName,
required void Function() opendrawer,
required double animationtime})
: super(key: key);
#override
_CalendarsPageState createState() {
return _DoctorCalendarsPageState();
}
}
class _CalendarsPageState extends State<CalendarsPage> {
late CalendarPlugin _CalendarPlugin;
List<Calendar> _calendar = [];
List<Calendar> get _writableCalendars =>
_calendar.where((c) => c.isReadOnly == false).toList();
List<Calendar> get _readOnlyCalendars =>
_calendar.where((c) => c.isReadOnly == true).toList();
_CalendarsPageState() {
_deviceCalendarPlugin = DeviceCalendarPlugin();
}
#override
void initState() {
super.initState();
_retrieveCalendars();
_CalendarsPageState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
flex: 1,
child: ListView.builder(
itemCount: _calendar.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
key: Key(_calendar[index].isReadOnly == true
? 'readOnlyCalendar${_readOnlyCalendars.indexWhere((c) => c.id == _calendar[index].id)}'
: 'writableCalendar${_writableCalendars.indexWhere((c) => c.id == _calendar[index].id)}'),
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorEventsPage(_calendar[index],
key: const Key('calendarEventsPage'));
}));
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
height: 22,
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
_calendar[index].name!,
style: Theme.of(context).textTheme.subtitle1,
),
),
Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(_calendar[index].color!)),
),
const SizedBox(width: 10),
// Container(
// margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0),
// padding: const EdgeInsets.all(3.0),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blueAccent)),
// // child: const Text('Default'),
// ),
Icon(_calendar[index].isReadOnly == true
? Icons.lock
: Icons.lock_open)
],
),
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final createCalendar = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorCalendarAddPage();
}));
if (createCalendar == true) {
_retrieveCalendars();
}
},
child: const Icon(Icons.add),
backgroundColor: Colors.green,
),
);
}
void _retrieveCalendars() async {
try {
var permissionsGranted = await _deviceCalendarPlugin.hasPermissions();
if (permissionsGranted.isSuccess &&
(permissionsGranted.data == null ||
permissionsGranted.data == false)) {
permissionsGranted = await _deviceCalendarPlugin.requestPermissions();
if (!permissionsGranted.isSuccess ||
permissionsGranted.data == null ||
permissionsGranted.data == false) {
return;
}
}
final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars();
setState(() {
_calendar = calendarsResult.data as List<Calendar>;
});
} on PlatformException catch (e) {
if (kDebugMode) {
print(e);
}
}
}
}
I am getting data from the network. I display a list with data. I have the ability to remove one element from a list using the removeInfo method. When deleting, the element does not disappear from the list, but it is deleted. How can I make sure that when one element is removed from the list, the entire page is not updated for me, but just the element is deleted? If I chose to delete - so that the element disappears and there is no need to refresh the page to see the changes.
body
BlocBuilder<InfoCubit, InfoState>(
builder: (context, state) {
final InfoCubit infoCubit =
BlocProvider.of<InfoCubit>(context);
if (state is InfoLoaded) {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: Padding(
padding: const EdgeInsets.only(top: 40, left: 14, right: 14),
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: state.info.length + 1,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: GestureDetector(
onTap: () {},
child: Container(
height: 112,
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: constants.Colors.greyMiddle,
),
child: Row(
children: [
IconButton(
onPressed: () {
if (state.info[index].status ==
true) {
showDialog(
context: context,
builder: (BuildContext context) {
return const PendingDialog();
},
);
} else if (state.info[index].status ==
null &&
!state.info[index].blocked) {
ShowCustomDialog()
.showBorderDialog(
barrierDismissible: false,
context: context,
child:
const DeleteInfoDialog(),
)
.then((value) async {
if (value) {
await infoCubit
.removeInfo(
infoId:
state.info[index].id,
userId: widget.userId,
)
.then((value) {
if (value) {
deleteNotification(
context);
}
});
}
});
} else {
try {
setState(() {
state.info[index].selected =
!state.info[index]
.selected;
});
} catch (_) {}
}
},
icon: state.info[index].selected
? SvgPicture.asset(
constants.Assets.remove2)
: state.info[index].status ==
true
? SvgPicture.asset(
constants.Assets.pending,
)
: state.info[index]
.status ==
false
? !state.info[index]
.blocked
? SvgPicture.asset(
constants
.Assets.remove,
color: constants
.Colors
.greyLight,
)
: SvgPicture.asset(
constants.Assets
.threeDot,
)
: SvgPicture.asset(
constants
.Assets.threeDot,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
),
);
),
),
),
);
// }
}
return const Center(
child: CircularProgressIndicator(
color: constants.Colors.purpleMain,
),
);
},
),
state
#immutable
abstract class InfoState {}
class InfoInitial extends InfoState {}
class InfoLoading extends InfoState {}
class InfoLoaded extends InfoState {
final List<InfoModel> info;
InfoLoaded(this.info);
}
class InfoDeletedSuccess extends InfoState {
final int infoId;
InfoDeletedSuccess(this.infoId);
}
class InfoError extends InfoState {}
cubit
class InfoCubit extends Cubit<InfoState> {
final InfoRepository _repository;
InfoCubit(this._repository) : super(InfoInitial());
Future loadPage(int userId) async {
try {
emit(InfoLoading());
List<InfoModel> info=
await _repository.getInfo(userId: userId);
emit(InfoLoaded(info));
} catch (_) {
emit(InfoError());
}
}
Future<bool> removeChargingStation(
{required int infoId, int? userId}) async {
try {
await _repository.removeInfo(
infoId: infoId);
if (userId != null) {
loadPage(userId);
}
// emit(InfoDeletedSuccess(chargingStationId));
return true;
} catch (_) {
return false;
}
}
void reload() => emit(StationSwitcherInitial());
}
I uses Listview.builder. it detect scroll end to many time that is why API call to many times and add duplicate Data in Listview.
Code:-
ListView.builder(
controller: _scrollController
..addListener(() async {
if (_scrollController
.position.pixels -
10 ==
_scrollController.position
.maxScrollExtent -
10 &&
!state.isPaginationLoading) {
print("Scroll End TEst Screen");
await ctx
.read<ProfileCubit>()
.getProfiles(
context, true, null);
}
Dont put logic code inside build. In your case _scrollController will addListener every times widget build called, cause multiple handle will trigger.
Advice for you is create and put handle logic to a function, put addListener/removeListener in initState/dispose because they was called only once.
With your problem, you can create a variale to check api was called yet and prevent other call.
class AppState extends State<App> {
var scroll = ScrollController();
var preventCall = false;
#override
initState() {
scroll.addListener(onScroll);
super.initState();
}
#override
void dispose() {
scroll.removeListener(onScroll);
super.dispose();
}
Future yourFuture() async {}
void onScroll() {
var position = scroll.position.pixels;
if (position >= scroll.position.maxScrollExtent - 10) {
if (!preventCall) {
yourFuture().then((_) => preventCall = false);
preventCall = true;
}
}
}
#override
Widget build(BuildContext context) {
return ...
}
}
You can add a condition to check if API call is happening or not and based on it you can you can call the API. You would also need to handle pagination logic if all info is loaded.
you can always check if you reached at the limit of max Limit of your scroll controller then you can call API
condition is like
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
you may be like full example
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
class Paggination2HomeScreen extends StatefulWidget {
const Paggination2HomeScreen({Key? key}) : super(key: key);
#override
State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}
class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
int _page = 1;
final int _limit = 10;
bool _hasNextPage = true;
bool _isFirstLoadRunning = false;
bool _isLoadMoreRunning = false;
List _posts = [];
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
final res = await http.get(
Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
);
if (res.statusCode == 200) {
setState(() {
_posts = jsonDecode(res.body);
});
log('receivedData : ${jsonDecode(res.body)}');
}
} catch (e) {
if (kDebugMode) {
log('Something went wrong');
}
}
setState(() {
_isFirstLoadRunning = false;
});
}
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false) {
setState(() {
_isLoadMoreRunning = true;
});
_page++;
try {
final res = await http.get(
Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
);
log('url : $_baseUrl?_page=$_page&_limit=$_limit');
if (res.statusCode == 200) {
final List fetchedPost = jsonDecode(res.body);
if (fetchedPost.isNotEmpty) {
setState(() {
_posts.addAll(fetchedPost);
});
} else {
setState(() {
_hasNextPage = false;
});
}
}
} catch (e) {
if (kDebugMode) {
log('something went wrong');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
// the controller for listyView
late ScrollController _controller;
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController();
}
#override
void dispose() {
super.dispose();
_controller.removeListener(_loadMore);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orangeAccent,
elevation: 0.0,
centerTitle: true,
title: Text(
'Paggination',
style: TextStyle(
color: Colors.black.withOpacity(0.52),
fontSize: 20,
),
),
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
),
body: _isFirstLoadRunning
? Center(
child: CircularProgressIndicator(
color: Colors.black.withOpacity(0.25),
),
)
: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
physics: const BouncingScrollPhysics(),
itemCount: _posts.length,
itemBuilder: (context, index) => Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.065),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black.withOpacity(0.2),
width: 0.5,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'ID : ${_posts.elementAt(index)['id']}',
),
Text(
'AlbumID : ${_posts.elementAt(index)['albumId']}',
),
Text(
'Title : ${_posts.elementAt(index)['title']}',
textAlign: TextAlign.justify,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['url'],
),
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['thumbnailUrl'],
),
),
],
),
],
),
),
),
),
// when the _loadMore running
if (_isLoadMoreRunning == true)
Container(
color: Colors.transparent,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: const Center(
child: CircularProgressIndicator(
color: Colors.amberAccent,
strokeWidth: 3,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
),
if (_hasNextPage == false)
SafeArea(
child: Container(
width: double.maxFinite,
padding: const EdgeInsets.only(top: 20, bottom: 20),
color: Colors.orangeAccent,
child: const Text('you get all'),
),
)
],
),
);
}
}
So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?
Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
So I am trying to refactor my listView logic. Basically my ListView has become cumbersome with the UI logic , so I decided, why not move certain parts of the UI logic to another class
This is my code
ListPage.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/ListTextArea.dart';
import 'package:sample_flutter_works/Model.dart';
import 'dart:convert';
import 'package:sample_flutter_works/RefreshTableContainer.dart';
class ListPage extends StatefulWidget {
#override
MyListPage createState() => MyListPage();
}
class MyListPage extends State<ListPage> {
MessageList messageList;
List<int> viewTimeInfo;
ScrollController _controller;
_scrollListener() {
}
#override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
_controller = ScrollController();
_controller.addListener(_scrollListener);
loadMessages(
completionBlock: (dataSet) => {
setState(() {
messageList = dataSet;
})
});
}
void loadMessages({completionBlock}) async {
var jsonString = await rootBundle.loadString('assets/Chat.json');
final jsonResponse = json.decode(jsonString);
if (jsonResponse != null) {
completionBlock(MessageList.fromJSON(jsonResponse));
} else {
completionBlock(null);
}
}
Widget listLayout() {
return ListView.separated(
padding: const EdgeInsets.all(8.0),
itemCount: (messageList != null && messageList.msgList != null)
? messageList.msgList.length
: 0,
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 4.0,
),
itemBuilder: (BuildContext context, int index) {
var msgValToSend =
(messageList != null && messageList.msgList != null)
? messageList.msgList[index]
: null;
return Stack(
children: <Widget>[
IntrinsicHeight(
child: Row(
children: <Widget>[
getTheImageLayout(msgValToSend),
new ListTextArea(
msg: msgValToSend,
didTapOnTextArea: tappedOnTextArea,
visibilityCheck: checkForVisibility)
],
),
)
],
);
});
}
tappedOnTextArea(Message msg) {
var viewedInfo = this.viewTimeInfo;
if (viewedInfo != null) {
var indexOfTappedElement = viewedInfo.indexOf(msg.messageID);
if (indexOfTappedElement != null && indexOfTappedElement != -1) {
viewedInfo.removeAt(indexOfTappedElement);
} else {
viewedInfo.add(msg.messageID);
}
} else {
viewedInfo = [msg.messageID];
}
setState(() {
viewTimeInfo = viewedInfo;
});
}
checkForVisibility(bool _visible, Message msg) {
if (msg != null && this.viewTimeInfo != null) {
var checkForIndex = this.viewTimeInfo.indexOf(msg.messageID);
if (checkForIndex != null && checkForIndex != -1) {
_visible = true;
}
}
}
Widget getTheImageLayout(Message msg) {
return Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.fromLTRB(5, 2.5, 0, 0),
child: Container(
color: Colors.red,
height: 50,
child: Row(
children: <Widget>[
userImageView(msg),
],
)),
)));
}
Widget userImageView(Message msg) {
return Expanded(
flex: 8,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: 40.0,
height: 40.0,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.green),
child: ClipOval(
child: Image.network(
(msg.msgUser.userPicUrl != null)
? msg.msgUser.userPicUrl
: 'https://picsum.photos/250?image=9',
fit: BoxFit.fill,
),
))));
}
Future<void> refreshTheChatTable() async {
print(" This is where the logic of pulll 2 refresh must be written ");
loadMessages(
completionBlock: (dataSet) => {
setState(() {
messageList = dataSet;
})
});
}
#override
Widget build(BuildContext context) {
return new RefreshTableContainer(
listLayout: listLayout(),
pull2RefreshAction: refreshTheChatTable,
);
}
}
ListTextArea.dart
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/Model.dart';
class ListTextArea extends StatelessWidget {
Message msg;
Function didTapOnTextArea;
Function visibilityCheck;
ListTextArea({
this.msg,
this.didTapOnTextArea,
this.visibilityCheck
});
#override
Widget build(BuildContext context) {
return Expanded(
flex: 8,
child: GestureDetector(
onTap: didTapOnTextArea(msg),
child: Padding(
padding: EdgeInsets.fromLTRB(0, 2.5, 10, 0),
child: Column(
children: getChildWidgetArray(msg) ,
),
),
));
}
List<Widget> getChildWidgetArray(Message msg) {
var elementalArray = [
Align(
alignment: Alignment.topLeft,
child: Text(
(msg != null) ? msg.msgContent.content : "Data Loading",
style: TextStyle(
background: Paint()..color = Colors.orange,
),
),
),
Spacer(), // Defaults to a flex of one.
Align(
alignment: Alignment.bottomRight,
child: Text(
'Date of sending',
textDirection: TextDirection.rtl,
style: TextStyle(
background: Paint()..color = Colors.blue,
),
),
)
];
var _visible = false;
visibilityCheck(_visible,msg);
var timeInfo = AnimatedOpacity (
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Align(
child: _visible ? (Align(alignment: Alignment.topLeft,child:Column(children: <Widget>[Text("Last Read :" + (msg.msgTimeInfo.lastReadInfo)),
Text("Delievered :" + (msg.msgTimeInfo.deliveredInfo))],))): null));
elementalArray.add(timeInfo);
return elementalArray;
}
}
The error is as follows:
What I am trying to do ( or had done earlier on when the entire code was in ListPage.dart ) was dynamically calculated cells in a listView, each cell responding to a tap action that shows in more data. I don't understand what I did wrong here at all.
I called the setState in init but inside a callback function. The statelesswidget ListTextArea will not handle the state at all, but returns the tapAction to the StateFulWidget ListPage.dart.
So why am I getting this error. Any insights would be helpful.
In my case, the error occurred when I was setting the state before build was complete, so, I deferred it to the next tick and it worked.
previously
myFunction()
New
Future.delayed(Duration.zero, () async {
myFunction();
});
The problem is in ListTextArea.dart, line
onTap: didTapOnTextArea(msg),
You are calling function didTapOnTextArea in build method instead of passing it as tap listener. You have to replace it with
onTap: (){
didTapOnTextArea(msg);
},