I'm making a task book, on a flutter, in this project I will use a lot of packages that I have not worked with before (this is not my first question on this project). My goal is to create tasks to add to the status (finished, not finished), and save them locally on the device. I wrote code (with some packages to help me figure it out) that does it all. But the problem arose when downloading questions from the local repository when starting the application. I have an error not to decode the data (in the repository I save a list of our tasks and status). Maybe someone faced such a problem I will be grateful for your help)
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:testjob/jsonTodo.dart';
void main() {
runApp(MaterialApp(
home: App(),
));
}
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 IconsType = Icons.wb_sunny;
late Color ColorType = Colors.black;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
List<TodoInfo> WidgetList = [];
@override
void dispose() {
textController.dispose();
popUpTextController.dispose();
super.dispose();
}
@override
void initState() {
getSP();
super.initState();
}
Future<void> addToSP(List<List<TodoInfo>> tList) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('todoLists', jsonEncode(tList));
}
void getSP() async {
final prefs = await SharedPreferences.getInstance();
final List<dynamic> jsonData =
jsonDecode(prefs.getString('todoLists') ?? '');
if (jsonData.isNotEmpty) {
for (var data in jsonData) {
final d = TodoInfo.fromJson(data);
WidgetList.add(d);
}
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, TodoInfo(todoText:textController.text.replaceAll(" ", ""), todoCheck: false ));
addToSP;
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 erorr
E/flutter (30831): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: FormatException: Unexpected end of input (at character 1)
E/flutter (30831):
E/flutter (30831): ^
E/flutter (30831):
E/flutter (30831): #0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1404:5)
E/flutter (30831): #1 _ChunkedJsonParser.close (dart:convert-patch/convert_patch.dart:522:7)
E/flutter (30831): #2 _parseJson (dart:convert-patch/convert_patch.dart:41:10)
E/flutter (30831): #3 JsonDecoder.convert (dart:convert/json.dart:506:36)
E/flutter (30831): #4 JsonCodec.decode (dart:convert/json.dart:157:41)
E/flutter (30831): #5 jsonDecode (dart:convert/json.dart:96:10)
E/flutter (30831): #6 AppState.getSP (package:testjob/main.dart:85:5)
E/flutter (30831): <asynchronous suspension>
E/flutter (30831):
CodePudding user response:
Change null check value from ''
to '[]'
.
You set jsonData
with type List. In case prefs.getString('todoLists')
is null, jsonDecode
will take ''
as parameter, but ''
is not a valid json and cause the FormatException.
// final List<dynamic> jsonData = jsonDecode(prefs.getString('todoLists') ?? '');
-> final List<dynamic> jsonData = jsonDecode(prefs.getString('todoLists') ?? '[]');
Updated: copy code in the file and paster to dartpad. since I don't have much time, I have omitted the things that are not so important, please add it after the main thread is up and running normally to run. check the dartpad console, you can see everytime you make an update to data, writeTodos was called, place your code in there (dartpad cant use sharedPref library)
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: App()));
class App extends StatefulWidget {
@override
AppState createState() => AppState();
}
class AppState extends State<App> {
final textController = TextEditingController();
final formKey = GlobalKey<FormState>();
List<TodoInfo> data = [];
@override
void initState() {
readTodos();
super.initState();
}
@override
void dispose() {
textController.dispose();
super.dispose();
}
void submitAddTodo() {
if (formKey.currentState?.validate() ?? false) {
final todoString = textController.text;
if (todoString.isNotEmpty) {
var todo = TodoInfo(todoText: todoString.trim(), todoCheck: false);
addTodo(todo);
textController.clear();
}
}
}
void removeTodoAt(int index) {
setState(() {
data = [...data]..removeAt(index);
writeTodos();
});
}
void updateTodoAt(int index, TodoInfo todo) {
setState(() {
data[index] = todo;
writeTodos();
});
}
void addTodo(TodoInfo todo) {
setState(() {
data = [todo, ...data];
writeTodos();
});
}
Future<void> writeTodos() async {
// write data to shared
print('writeTodos');
}
void readTodos() async {
print('readTodos');
// read data from your shared insteads hard code `prefsData`
final prefsData = [
TodoInfo(todoText: 'todo 1', todoCheck: false),
TodoInfo(todoText: 'todo 2', todoCheck: false),
];
setState(() => data = prefsData);
}
@override
Widget build(BuildContext context) {
return Scaffold(
//...
body: Form(
key: formKey,
child: 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: submitAddTodo,
)
],
),
),
Container(
width: MediaQuery.of(context).size.height * 0.45,
child: TextFormField(
style: TextStyle(fontSize: 22.0),
controller: textController,
autofocus: true,
validator: (value) {
if (value == null || value.isEmpty) return 'Список задач';
},
),
),
Expanded(child: _rTodos())
],
),
),
);
}
Widget _rTodos() {
var wgs = <Widget>[];
for (int i = 0; i < data.length; i ) {
var todo = data[i];
wgs.add(GestureDetector(
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
value: todo.todoCheck,
title: Text(todo.todoText, style: TextStyle(fontSize: 22.0)),
onChanged: (checkValue) =>
updateTodoAt(i, todo..todoCheck = checkValue ?? false),
),
));
}
return ListView(
children: wgs
);
}
}
class TodoInfo {
String todoText;
bool todoCheck;
TodoInfo({required this.todoText, required this.todoCheck});
}