Every now and then, your app might encounter an error which would break your app on runtime. These errors cannot be avoided as such widgets rely on dynamic inputs and data, which is why today I am going to cover how to catch errors in Flutter to mitigate future errors in your Flutter app. Let’s get started!
Suppose I have an app below. It is a To-Do list app. I made it quite simple. We were going to use it in this scenario when it encounters an error.
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.amber,
title: Text('Todo'),
),
body: TodoList(),
);
}
}
class TodoList extends StatefulWidget {
const TodoList({Key? key}) : super(key: key);
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
List<dynamic> data = <dynamic>[];
void getData() async {
Response response = await get(
Uri.parse('https://jsonplaceholder.typicode.com/todos'), // error might occur here when URL is invalid
);
data = jsonDecode(response.body);
}
@override
Widget build(BuildContext context) {
getData();
return Center(
child: ListView.builder(
itemCount: data.length, // assign length of the data here
itemBuilder: (context, index) { // returns a Card widget
return Card(
child: ListTile(
leading: Text(
data[index]['id'].toString(), // assigned data's id here
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
title: Text(
data[index]['title'], // assigned data's title here
),
trailing: CupertinoSwitch(
value: data[index]['completed'], // assigned data's completed status here
onChanged: (bool status) {},
activeColor: Colors.amber,
),
),
);
}),
);
}
}
What if this app became complex and the URL was dynamically inputted and it was occasionally fed an invalid URL? It would cause a massive error that would stop our app from working properly.
Visibility Widget
Introducing the Visibility widget for Flutter. This widget will display a different widget using its “replacement” argument when the “visible” argument is set to false. You can check out the documentation here (https://api.flutter.dev/flutter/widgets/Visibility-class.html).
Here’s the code example of it:
Visibility(
child: Text("Working display here"),
replacement: const SizedBox(
height: 50,
child: Text(
"Error display here",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
visible: isValid, // assign variable here for visibility
);
In addition, we need a block of code that can prevent errors in Dart.
Try-Catch Block
An exception (or exceptional event) is a problem that occurs during program execution. When an exception occurs, the normal flow of the program is disrupted, and the program/application exits abnormally. That is why the try-catch block is required.
The try block embeds code that might possibly result in an exception. The on block is used when the exception type needs to be specified. The catch block is used when the handler needs the exception object.
You can check out this article here: https://www.tutorialspoint.com/dart_programming/dart_programming_exceptions.htm
Application
Now that we know how to handle errors, I am going to apply it to my app below.
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.amber,
title: Text('Todo'),
),
body: TodoList(),
);
}
}
class TodoList extends StatefulWidget {
const TodoList({Key? key}) : super(key: key);
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
List<dynamic> data = <dynamic>[];
bool valid = true;
void getData() async {
try{
Response response = await get(
Uri.parse('https://jsonplaceholder.typicode.com/todo'),
);
data = jsonDecode(response.body);
}catch (e) {
setState(() {
valid = false;
});
}
// print(data); // will print a list of datasets
}
@override
Widget build(BuildContext context) {
getData();
return Visibility(
child: Center(
child: ListView.builder(
itemCount: data.length, // assign length of the data here
itemBuilder: (context, index) {
// returns a Card widget
return Card(
child: ListTile(
leading: Text(
data[index]['id'].toString(), // assigned data's id here
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
title: Text(
data[index]['title'], // assigned data's title here
),
trailing: CupertinoSwitch(
value: data[index]
['completed'], // assigned data's completed status here
onChanged: (bool status) {},
activeColor: Colors.amber,
),
),
);
}),
),
replacement: const SizedBox(
height: 50,
child: Center(
child: Text(
"Cannot load data",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
),
visible: valid,
);
}
}
As you can see, to explain the code I wrote above, first I wrapped the http response with a try-catch block to prevent a runtime error when the url it was passed was invalid. In addition, I created a new boolean variable for indication on our “Visibility” widget. When an error occurs, it will be set to “false” using the “setState()” function. To conclude, the replacement argument’s child will be displayed on the screen, resulting in a display that says “Cannot load data”.
Conclusion
An exception (or exceptional event) is a problem that occurs during program execution. Utilizing the “Visibility” widget of Flutter and the Try-Catch block of Dart, we now know how to handle errors. We can apply it to our Flutter app projects to mitigate the runtime flow of the app, which prevents breaking the app. Thank you for reading. Keep following me on this Flutter journey.