Tutorial 8: Flutter Networking, Authentication, and Integration
Platform-Based Programming (CSGE602022) — organized by the Faculty of Computer Science, University of Indonesia, Odd Semester 2023/2024
Learning Objectives
After completing this tutorial, students are expected to:
- Understand the structure and creation of models in Flutter.
- Understand how to fetch, process, and display data from web services.
- Understand basic state management using Provider in Flutter.
- Able to authenticate with the Django web service with the Flutter application.
Model in Flutter
In this tutorial, we will call a web service and display the results on the Flutter page we created. However, before making the web service call, we need to define the model we use for the web service call. In Flutter, models are defined using a class, similar to what has been learned in the OOP section of Programming Foundations 2.
The code below is just an example. However, we highly recommend you to read it because the concepts will be used in the following sections.
Here is an example of class in Flutter.
class Car {
Car({
this.id,
this.brand,
this.model,
this.color,
});
int id;
String brand;
String model;
String color;
}
Note: If you encounter errors while creating the class, add the required
keyword to each class parameter in the constructor section.
So far, we have successfully created the class. Next, we will add some codes to form a Car
model. This Car
model represents the response from the web service call.
Import dart:convert
at the top of the file.
import 'dart:convert';
...
In the Car
class, add the following code.
factory Car.fromJson(Map<String, dynamic> json) => Car(
id: json["id"],
brand: json["brand"],
model: json["model"],
color: json["color"],
);
Map<String, dynamic> toJson() => {
"id": id,
"brand": brand,
"model": model,
"color": color,
};
Add the following code outside the Car
class.
Car carFromJson(String str) => Car.fromJson(json.decode(str));
String carToJson(Car data) => json.encode(data.toJson());
The final code will look like this. It will display a single Car
object from the web service.
import 'dart:convert';
Car carFromJson(String str) => Car.fromJson(json.decode(str));
String carToJson(Car data) => json.encode(data.toJson());
class Car {
Car({
this.id,
this.brand,
this.model,
this.color,
});
int id;
String brand;
String model;
String color;
factory Car.fromJson(Map<String, dynamic> json) => Car(
id: json["id"],
brand: json["brand"],
model: json["model"],
color: json["color"],
);
Map<String, dynamic> toJson() => {
"id": id,
"brand": brand,
"model": model,
"color": color,
};
}
Here is an explanation of the above code.
There are additional codes such as the toJson
and fromJson
methods inside the Car
class. This is because when we make a request to a web service with the GET method, we usually receive the result in the form of JSON. Therefore, we need to convert the data with the fromJson
method so that Flutter recognizes this JSON as an object of the Car
class. Additionally, there is also the toJson
method, which will be used when sending data to the web service (such as POST or PUT).
Here is an example response from a web service with the GET method that can be converted into the Car
model.
{
"id": 1,
"brand": "Honda",
"model": "Civic",
"color": "Yellow"
}
Now, what if the response from the web service consists of a collection of JSON objects? It is actually the same as the code above, but with some modifications to the carFromJson
and carToJson
methods.
The code is as follows.
List<Car> carsFromJson(String str) => List<Car>.from(json.decode(str).map((car) => Car.fromJson(car)));
String carsToJson(List<Car> data) => json.encode(List<dynamic>.from(data.map((car) => car.toJson())));
Here is an example response from a web service with the GET method that can be converted into the Car
model.
[
{
"id": 1,
"brand": "Honda",
"model": "Civic",
"color": "Yellow"
},
{
"id": 2,
"brand": "Toyota",
"model": "Supra",
"color": "Red"
}
]
Fetching Data from Web Service in Flutter
During application development, there are times when we need to fetch external data from outside our application (the Internet) to display in our application. This tutorial aims to understand how to fetch data from a web service in Flutter.
In general, there are several steps when you want to display data from another web service to a Flutter application, namely:
Add the
http
dependency to the project; this dependency is used for exchanging HTTP requests.Create a model according to the response from the data originating from that web service.
Make an HTTP request to the web service using the
http
dependency.Convert the object obtained from the web service to the model we created in step two.
Display the converted data to the application using
FutureBuilder
.
For further explanation, you can read the details here.
Basic State Management using Provider
Provider
is a wrapper around InheritedWidget
to make InheritedWidget
easier to use and more reusable. InheritedWidget
itself is the basic class for Flutter widgets that efficiently spread information to other widgets in one tree.
The benefits of using provider
are as follows:
- Allocating resources becomes simpler.
- Lazy-loading.
- Reducing boilerplate every time a new class is created.
- Supported by Flutter Devtools so that
provider
can be tracked from Devtool. - Scalability improvement for classes that utilize complex built-in listening mechanisms.
For more information about provider
, please visit the Provider package page.
Tutorial: Django-Flutter Authentication Integration
Set Up Authentication in Django for Flutter
Follow the steps below to integrate the authentication system in Django.
Create a
django-app
namedauthentication
in your Django project you created earlier.Add
authentication
toINSTALLED_APPS
in the main projectsettings.py
file of your Django application.Run the command
pip install django-cors-headers
to install the required library.Add
corsheaders
toINSTALLED_APPS
in the main projectsettings.py
file of your Django application.Add
corsheaders.middleware.CorsMiddleware
toMIDDLEWARE
in the main projectsettings.py
file of your Django application.Add the following variables to the main project
settings.py
file of your Django application.CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SAMESITE = 'None'Create a view method for login in
authentication/views.py
.from django.shortcuts import render
from django.contrib.auth import authenticate, login as auth_login
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def login(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
auth_login(request, user)
# Successful login status.
return JsonResponse({
"username": user.username,
"status": True,
"message": "Login successful!"
# Add other data if you want to send data to Flutter.
}, status=200)
else:
return JsonResponse({
"status": False,
"message": "Login failed, account disabled."
}, status=401)
else:
return JsonResponse({
"status": False,
"message": "Login failed, check email or password again."
}, status=401)Create a
urls.py
file in theauthentication
folder and add URL routing to the function created with thelogin/
endpoint.from django.urls import path
from authentication.views import login
app_name = 'authentication'
urlpatterns = [
path('login/', login, name='login'),
]Finally, add
path('auth/', include('authentication.urls'))
, to theshopping_list/urls.py
file.
Integrate Authentication System in Flutter
To facilitate the creation of the authentication system, the teaching assistant team has created a Flutter package that can be used to contact the Django web service (including GET
and POST
operations).
The package can be accessed via the following link: pbp_django_auth
Follow the steps below to integrate the authentication system into Flutter.
Install the package provided by the teaching assistant team by running the following commands in the Terminal.
flutter pub add provider
flutter pub add pbp_django_authTo use the package, you need to modify the root widget to provide the
CookieRequest
library to all child widgets usingProvider
.For example, if your application was like this before:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: MyHomePage()),
);
}
}Change it to:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider(
create: (_) {
CookieRequest request = CookieRequest();
return request;
},
child: MaterialApp(
title: 'Flutter App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: MyHomePage()),
),
);
}
}This will create a new
Provider
object that will share an instance ofCookieRequest
with all components in the application.Create a new file in the screens folder named
login.dart
.Fill the
login.dart
file with the following code.import 'package:shopping_list/screens/menu.dart';
import 'package:flutter/material.dart';
import 'package:pbp_django_auth/pbp_django_auth.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const LoginApp());
}
class LoginApp extends StatelessWidget {
const LoginApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final request = context.watch<CookieRequest>();
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Container(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: 'Username',
),
),
const SizedBox(height: 12.0),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
),
obscureText: true,
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () async {
String username = _usernameController.text;
String password = _passwordController.text;
// Check credentials
// TODO: Change the URL and don't forget to add a trailing slash (/) at the end of the URL!
// To connect the Android emulator to Django on localhost,
// use the URL http://10.0.2.2/
final response = await request.login("http://<YOUR_APP_URL>/auth/login/", {
'username': username,
'password': password,
});
if (request.loggedIn) {
String message = response['message'];
String uname = response['username'];
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => MyHomePage()),
);
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(content: Text("$message Welcome, $uname.")));
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Login Failed'),
content:
Text(response['message']),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
);
}
},
child: const Text('Login'),
),
],
),
),
);
}
}In the
main.dart
file, in theMaterialApp(...)
widget, changehome: MyHomePage()
tohome: LoginPage()
.Run your Flutter application and try to log in.
Custom Model Creation
In creating a model that adapts to JSON data, we can use the Quicktype website with the following steps.
Open the
JSON
endpoint you created earlier in tutorial 2.Copy the
JSON
data and open the Quicktype website.On the Quicktype website, change the setup name to
Product
, source type toJSON
, and language toDart
.Paste the previously copied
JSON
data into the textbox provided on Quicktype.Click the
Copy Code
option on Quicktype.Here is an example result.
After obtaining the model code through Quicktype, open the Flutter project again, create a new file in the lib/models
folder named product.dart
, and paste the copied code from Quicktype.
Fetch Data from Django and Show Data in the Flutter App
Add HTTP Dependency
To create HTTP requests, we need the http package.
Run
flutter pub add http
in your Flutter project terminal to add thehttp
package.In the file
android/app/src/main/AndroidManifest.xml
, add this code to allow your Flutter app to access the internet....
<application>
...
</application>
<!-- Required to fetch data from the Internet. -->
<uses-permission android:name="android.permission.INTERNET" />
...
Fetch Data from Django
Create a new file in the
lib/screens
directory with namelist_product.dart
.In the file
list_product.dart
, import the necessary libraries. Change the<APP_NAME>
to your Flutter project app name.import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:<APP_NAME>/models/product.dart';
...Add this code to the file
list_product.dart
. Do not forget to import the necessary files....
import 'package:<APP_NAME>/widgets/left_drawer.dart';
class ProductPage extends StatefulWidget {
const ProductPage({Key? key}) : super(key: key);
@override
_ProductPageState createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
Future<List<Product>> fetchProduct() async {
// TODO: Change the URL to your Django app's URL. Don't forget to add the trailing slash (/) if needed.
var url = Uri.parse(
'http://<YOUR_DJANGO_APP_URL>/json/');
var response = await http.get(
url,
headers: {"Content-Type": "application/json"},
);
// decode the response to JSON
var data = jsonDecode(utf8.decode(response.bodyBytes));
// convert the JSON to Product object
List<Product> list_product = [];
for (var d in data) {
if (d != null) {
list_product.add(Product.fromJson(d));
}
}
return list_product;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product'),
),
drawer: const LeftDrawer(),
body: FutureBuilder(
future: fetchProduct(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return const Center(child: CircularProgressIndicator());
} else {
if (!snapshot.hasData) {
return const Column(
children: [
Text(
"No product data available.",
style:
TextStyle(color: Color(0xff59A5D8), fontSize: 20),
),
SizedBox(height: 8),
],
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (_, index) => Container(
margin: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${snapshot.data![index].fields.name}",
style: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text("${snapshot.data![index].fields.price}"),
const SizedBox(height: 10),
Text(
"${snapshot.data![index].fields.description}")
],
),
));
}
}
}));
}
}Add the page
list_product.dart
towidgets/left_drawer.dart
.// ListTile Menu Code
...
ListTile(
leading: const Icon(Icons.shopping_basket),
title: const Text('Product List'),
onTap: () {
// Route menu to product page
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ProductPage()),
);
},
),
...Change the function of the
View Products
button in the main page to redirect the page toProductPage
. You can add the redirection by addingelse if
after theif(...){...}
code at the end of theonTap: () {...}
code in thewidgets/shop_card.dart
file....
else if (item.name == "View Products") {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const ProductPage()));
}
...Import the necessary files while adding the
ProductPage
to theleft_drawer.dart
andshop_card.dart
.Run the app and try to add some products. Check the result in the new
List Product
page.
Integrate Flutter Form with the Django Service
Do these steps in your Django project.
Create a new views function in the
main/views.py
file. Fill it with this code.@csrf_exempt
def create_product_flutter(request):
if request.method == 'POST':
data = json.loads(request.body)
new_product = Product.objects.create(
user = request.user,
name = data["name"],
price = int(data["price"]),
description = data["description"]
)
new_product.save()
return JsonResponse({"status": "success"}, status=200)
else:
return JsonResponse({"status": "error"}, status=401)Add new path in the
main/urls.py
file with this code.path('create-flutter/', create_product_flutter, name='create_product_flutter'),
Rerun (and redeploy) your app. If you have deployed your app before, any data and transactions will be lost after the redeployment.
Do these steps in your Flutter project.
Connect the page
shoplist_form.dart
toCookieRequest
with this code....
@override
Widget build(BuildContext context) {
final request = context.watch<CookieRequest>();
return Scaffold(
...Change the logic in the
onPressed: ()
block to:...
onPressed: () async {
if (_formKey.currentState!.validate()) {
// Send request to Django and wait for the response
// TODO: Change the URL to your Django app's URL. Don't forget to add the trailing slash (/) if needed.
final response = await request.postJson(
"http://<YOUR_DJANGO_APP_URL>/create-flutter/",
jsonEncode(<String, String>{
'name': _name,
'price': _price.toString(),
'description': _description,
// TODO: Adjust the fields with your Django model
}));
if (response['status'] == 'success') {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text("New product has saved successfully!"),
));
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => MyHomePage()),
);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content:
Text("Something went wrong, please try again."),
));
}
}
},
...Do some quick fixes by importing the necessary files.
Rerun your app and try add new transactions from your Flutter app.
Implement Logout Feature
Do these steps in your Django project.
Add a new views method for logout in the
authentication/views.py
.from django.contrib.auth import logout as auth_logout
...
@csrf_exempt
def logout(request):
username = request.user.username
try:
auth_logout(request)
return JsonResponse({
"username": username,
"status": True,
"message": "Logged out successfully!"
}, status=200)
except:
return JsonResponse({
"status": False,
"message": "Logout failed."
}, status=401)Add a new path in the
authentication/urls.py
file.path('logout/', logout, name='logout'),
Do these steps in the Flutter project.
Add the
lib/widgets/shop_card.dart
file and add this code. Do not forget to resolve the import issues....
@override
Widget build(BuildContext context) {
final request = context.watch<CookieRequest>();
return Material(
...Change the
onTap: () {...}
for the widgetInkwell
toonTap: () async {...}
. This allowsInkwell
do the logout process asynchronously.Add this code to the last part of the
async {...}
block:...
// previous if statement
// add the else if statement below
else if (item.name == "Logout") {
final response = await request.logout(
// TODO: Change the URL to your Django app's URL. Don't forget to add the trailing slash (/) if needed.
"http://<YOUR_DJANGO_APP_URL>/auth/logout/");
String message = response["message"];
if (response['status']) {
String uname = response["username"];
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("$message Good bye, $uname."),
));
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginPage()),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("$message"),
));
}
}
...Rerun the app and try to logout.
Closing
Congratulations! You have completed the Tutorial 8! Hopefully with this tutorial, you can understand more about models, data fetching, basic state management, and Django-Flutter integration. 😄
Don't forget to complete the TODO!!
Run the following commands to
add
,commit
, andpush
:git add .
git commit -m "<commit_message>"
git push -u origin <main_branch>- Replace
<commit_message>
with your desired message. For example:git commit -m "Completed tutorial 8"
. - Replace
<your_main_branch>
with your main branch name. For example:git push -u origin main
orgit push -u origin master
.
- Replace
Additional References
- Fetch Data From the Internet
- How to create models in Flutter Dart
- Simple app state management | Flutter
- Flutter State Management with Provider
- Pengenalan State Management Flutter dan Jenis-jenisnya
Contributor
- Yudi Putra Sabri
- Muhammad Falensi Azmi
- James Smith Wigglesworth
- Adjie Djaka Permana
- Aidah Novallia Putri (EN Translator)
- Bonaventura Galang (EN Translator)
- Ferry (EN Translator)
Credits
This tutorial was developed based on PBP Odd 2023 and PBP Even 2023 written by the 2023 Platform-Based Programming Teaching Team. All tutorials and instructions included in this repository are designed so that students who are taking Platform-Based Programming courses can complete the tutorials during lab sessions.