Courses

[NEW] Flutter 3 Mobile App with Laravel 12 API

Adding Login Functionality

Now that we have a Registration form, we need our users to be able to log inLogin:

Here's what we need to do:

  1. We must install a device_info_plus package to get Device information.
  2. We need a login() function on our Auth Provider.
  3. We need to add a Login form.

Installing New Package

First, we will need a package to be installed. This will be used to get the device name from the device. It is required by Laravel Sanctum to be passed in registration/login (as it is used to create named tokens):

flutter pub add device_info_plus

Adding API Function

Next, we need to define our API function in API Service:

// ...
 
Future<String> login(
String email, String password, String device_name) async {
String url = '$baseUrl/api/auth/login';
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json',
},
body: jsonEncode(<String, String>{
'email': email,
'password': password,
'device_name': device_name,
}),
);
 
if (response.statusCode == 422) {
final Map<String, dynamic> data = json.decode(response.body);
final Map<String, dynamic> errors = data['errors'];
String message = '';
errors.forEach((key, value) {
value.forEach((error) {
message += '$error\n';
});
});
 
throw Exception(message);
}
 
return response.body;
}
 
// ...

Adding Login Functionality to Auth Provider

And, of course, we need to add a similar function to our Auth Provider:

// ...
 
Future<String> register(String name, String email, String password,
String password_confirmation, String device_name) async {
String token = await apiService.register(
name, email, password, password_confirmation, device_name);
isAuthenticated = true;
notifyListeners();
 
return token;
}
 
Future<String> login(
String email, String password, String device_name) async {
String token = await apiService.login(email, password, device_name);
isAuthenticated = true;
notifyListeners();
 
return token;
}
 
// ...

Adding Device Information to Registration

We have to modify our Registration to also include a device_name:

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:device_info_plus/device_info_plus.dart';
 
// ...
class RegisterState extends State<Register> {
// ...
late String deviceName;
 
@override
void initState() {
super.initState();
getDeviceName();
}
 
Future<void> getDeviceName() async {
try {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
setState(() {
deviceName = androidInfo.model;
});
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
setState(() {
deviceName = iosInfo.name;
});
}
} catch (e) {
setState(() {
deviceName = 'Could not retrieve device name';
});
}
}
 
Future<void> submit() async {
final form = _formKey.currentState;
if (!form!.validate()) {
return;
}
final AuthProvider provider =
Provider.of<AuthProvider>(context, listen: false);
try {
String token = await provider.register(
nameController.text,
emailController.text,
passwordController.text,
confirmPasswordController.text,
'Some device name');
deviceName);
Navigator.pop(context);
} catch (Exception) {
setState(() {
errorMessage = Exception.toString().replaceAll('Exception: ', '');
});
}
}
 
// ...

Adding Login Form

Now it's time to add our Login form. There are many things to do here, but it follows the same pattern as the Registration form. So, we'll skip the line-by-line explanation:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:laravel_api_flutter_app/providers/auth_provider.dart';
import 'package:provider/provider.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'dart:io';
 
class Login extends StatefulWidget {
const Login({super.key});
 
@override
LoginState createState() => LoginState();
}
 
class LoginState extends State< Login> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final emailController = TextEditingController();
final passwordController = TextEditingController();
 
String errorMessage = '';
 
late String deviceName;
 
@override
void initState() {
super.initState();
getDeviceName();
}
 
Future<void> getDeviceName() async {
try {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
setState(() {
deviceName = androidInfo.model;
});
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
setState(() {
deviceName = iosInfo.name;
});
}
} catch (e) {
setState(() {
deviceName = 'Could not retrieve device name';
});
}
}
 
Future<void> submit() async {
final form = _formKey.currentState;
if (!form!.validate()) {
return;
}
final AuthProvider provider =
Provider.of<AuthProvider>(context, listen: false);
try {
String token = await provider.login(
emailController.text, passwordController.text, deviceName);
} catch (Exception) {
setState(() {
errorMessage = Exception.toString().replaceAll('Exception: ', '');
});
}
}
 
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Container(
color: Theme.of(context).primaryColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Card(
elevation: 0,
margin: EdgeInsets.only(left: 20, right: 20),
child: Padding(
padding: EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
controller: emailController,
validator: (String? value) {
// Validation condition
if (value!.trim().isEmpty) {
return 'Please enter email';
}
return null;
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email',
),
),
SizedBox(height: 20), // Acts as a spacer
TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: passwordController,
obscureText: true,
autocorrect: false,
enableSuggestions: false,
validator: (String? value) {
// Validation condition
if (value!.isEmpty) {
return 'Please enter password';
}
return null;
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
),
SizedBox(height: 20), // Acts as a spacer
ElevatedButton(
onPressed: () {
submit();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
minimumSize: Size(double.infinity, 40),
),
child: Text('Login'),
),
Text(errorMessage,
style: TextStyle(color: Colors.red)),
Padding(
padding: EdgeInsets.only(top: 20),
// Different way to add padding
child: InkWell(
child: Text('Register new User'),
onTap: () =>
Navigator.pushNamed(context, '/register')),
)
],
),
),
),
)
],
)));
}
}

That's it! We should now be able to log in with our registered user.

Note: Refreshing the app will log you out. In a few lessons, we will add a way to persist the login state.


In the next lesson, we will add API token support to our API service. We skipped it because we did not have a way to Log in. Now that we have a login form, we can add API token support and revert some Laravel code changes.


Check out the GitHub Commit for this lesson.

Previous: Adding Registration Functionality with API Usage
avatar
Luis Antonio Parrado

I got some compatibility issues with the device_info_plus package and the Android NDK version, to be specific this was the error message:

Your project is configured with Android NDK 26.3.11579264, but the following plugin(s) depend on a different Android NDK version:
device_info_plus requires Android NDK 27.0.12077973
Fix this issue by using the highest Android NDK version (they are backward compatible).
Add the following to C:\Users\luisp\Documents\develop\projects\mobile\flutter\<my-project>\android\app\build.gradle.kts:

    android {
        ndkVersion = "27.0.12077973"
        ...
    }

My Solution Change the Android NDK version directly in the Flutter SDK, right in the file:

<flutter-sdk-path>\packages\flutter_tools\gradle\src\main\groovy\flutter.groovy

searching the line

public final String ndkVersion = "26.3.11579264"

and replace it by

public final String ndkVersion = "27.0.12077973"
avatar
You can use Markdown
avatar
You can use Markdown