Flutter Mobile App with Flask REST API Authentication
Flutter App Connecting to Flask API
(Secure Login, and Role-Based Access Control)
Flutter Mobile App + Flask REST API | Login Authentication with JWT | Full Practical Demo:
Today's, we are building a simple Flutter mobile app that connects to our
Flask REST API for user authentication.
We’ll
cover login, token-based access, and protected dashboard access — all using
Flutter and Flask.
We
built two simple screens:"
Login Screen — takes username and
password and calls /login API.
Dashboard Screen — calls /dashboard API
with JWT Token after successful login.
Flutter:
Flutter
is an open-source UI software development toolkit created by Google for
building cross-platform applications from a single codebase.
What
Flutter Does?
Builds native-compiled apps for:
📱 Mobile: iOS & Android
🖥️ Desktop: Windows, macOS, Linux
🌐 Web: Browser-based apps
Uses Dart (a modern, fast programming language also by Google).
Why
Choose Flutter?
🚀 Fast Development: Hot reload saves hours.
🎨 Beautiful
UIs: Customizable widgets.
📦 One
Codebase: Maintain iOS/Android/web simultaneously.
💡 Backed by Google: Strong
community & updates.
What
is an AVD?
AVD
(Android Virtual Device) is an emulator that lets you test Android apps on your
computer without needing a physical device.
- A software replica of a physical Android device (phone, tablet, etc.).
- Runs on your PC/Mac to simulate different Android versions, screen sizes, and hardware configurations.
- Part of Android Studio (the official IDE for Android development).
Key Uses:
📱 Test apps before deploying to real
devices.
🔍 Debug UI,
performance, and compatibility.
🌍 Simulate different
Android versions (e.g., Android 12, 13) and hardware (RAM, sensors).
How
to Create an AVD?
1. Open
Android Studio → Go to AVD Manager:
2. Click
Create Virtual Device.
3. Select
a device model (e.g., Pixel 6).
4. Choose
a system image (Android version, e.g., Android 35).
5. Configure
RAM, storage, and other settings.
6. Click
Finish
Enable Virtualization in
BIOS
(✅ Most important for
emulators!)
Restart your PC → Enter
BIOS (usually F2, Delete, or F10 at boot).
Find "Intel
Virtualization Technology" (VT-x) and enable it.
Save and exit BIOS.
1. Install Android Studio
Download
and install Android Studio:
👉 https://developer.android.com/studio
This
will install:
- Android Emulator
- Android SDK
- AVD Manager (for managing virtual devices)
2. Install Flutter SDK (Recommended for Mobile App)
We'll
use Flutter (Google's framework) — works for both Android and iOS.
Install
Flutter:
👉
https://docs.flutter.dev/get-started/install
After installing Flutter:
Run
in terminal: flutter doctor
Fix
any missing requirements (it will guide you).
3.
Set Flutter SDK Path in Android Studio
Open
Android Studio → Settings → Languages & Frameworks → Flutter.
Set
the Flutter SDK path (where Flutter was installed).
Example:
C:\flutter or wherever you installed it.
Click
Apply and OK.
4.
Install Flutter and Dart Plugins
Open
Android Studio → Settings → Plugins.
Search
for Flutter and Dart plugins and install them.
Restart
Android Studio.
1. Project Structure
/mobileapp_flask_api
├── lib/
│ ├── main.dart
│ ├── screens/
│ │ ├── login_screen.dart
│ │ └── dashboard_screen.dart
│ ├── services/
│ │ └── api_service.dart
└── pubspec.yaml
The required code is as follows:
//main.dart
import 'package:flutter/material.dart';
import 'screens/login_screen.dart';
//import 'screens/register_screen.dart';
import 'screens/dashboard_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter API Client',
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (context) => const LoginScreen(),
// '/register': (context) => const RegisterScreen(),
'/dashboard': (context) => const DashboardScreen(), // << No arguments!
},
);
}
}
//api_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class ApiService {
static const String baseUrl = "http://10.0.2.2:5000"; // <-- Replace with your Flask API
static final storage = FlutterSecureStorage();
static Future<String?> login(String username, String password) async {
final response = await http.post(
Uri.parse('$baseUrl/login'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"username": username, "password": password}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
await storage.write(key: "token", value: data["access_token"]);
return null; // Success
} else {
final data = jsonDecode(response.body);
return data["error"] ?? "Login failed";
}
}
static Future<String?> register(String username, String password, String role) async {
final response = await http.post(
Uri.parse('$baseUrl/register'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({"username": username, "password": password, "role": role}),
);
if (response.statusCode == 200) {
return null; // Success
} else {
final data = jsonDecode(response.body);
return data["error"] ?? "Registration failed";
}
}
static Future<String?> dashboard() async {
String? token = await storage.read(key: "token");
if (token == null) return "Unauthorized";
final response = await http.get(
Uri.parse('$baseUrl/dashboard'),
headers: {"Authorization": "Bearer $token"},
);
if (response.statusCode == 200) {
return jsonDecode(response.body)["message"];
} else {
return "Access denied.";
}
}
static Future<String?> adminPanel() async {
String? token = await storage.read(key: "token");
if (token == null) return "Unauthorized";
final response = await http.get(
Uri.parse('$baseUrl/admin'),
headers: {"Authorization": "Bearer $token"},
);
if (response.statusCode == 200) {
return jsonDecode(response.body)["message"];
} else {
return "Access denied.";
}
}
static Future<void> logout() async {
await storage.delete(key: "token");
}
}
//login_screen.dart
import 'package:flutter/material.dart';
import '../services/api_service.dart'; // Adjust the path
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
bool isLoading = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
controller: usernameController,
decoration: const InputDecoration(labelText: 'Username'),
),
TextField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: isLoading ? null : _login,
child: isLoading ? const CircularProgressIndicator() : const Text('Login'),
),
],
),
),
);
}
Future<void> _login() async {
setState(() {
isLoading = true;
});
String username = usernameController.text.trim();
String password = passwordController.text.trim();
String? error = await ApiService.login(username, password);
if (!mounted) return; // <-- ⭐ very important now!
setState(() {
isLoading = false;
});
if (error == null) {
Navigator.pushReplacementNamed(context, '/dashboard');
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error)),
);
}
}
}
//dashboard_screen.dart
import 'package:flutter/material.dart';
import '../services/api_service.dart'; // Import your ApiService
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
String message = "Loading...";
@override
void initState() {
super.initState();
fetchDashboardMessage();
}
void fetchDashboardMessage() async {
String? response = await ApiService.dashboard(); // API call
setState(() {
message = response ?? "Failed to load dashboard.";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
ApiService.logout(); // Clear token
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);
},
)
],
),
body: Center(
child: Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24),
),
),
);
}
}
name: mobileapp_flask_api
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
//pubspec.yaml
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.7.2
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
flutter_secure_storage: ^9.0.0 # For storing JWT token securely
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
RESTful
API Services:
RESTAPI can be consumed by both Web Clients (browsers, JavaScript apps) and mobile
clients (Android/iOS apps).
Why
This Matters?
A
well-designed REST API lets you share business logic across Web App and Mobile Apps, reducing development time and ensuring consistency.

No comments: