Continue from previous tutorial: https://hashnotes.hashnode.dev/flutter-login-api-request
[1] Edit PubSpec
Add date time format library i.e. intl (Provides internationalization and localization facilities, including message translation, plurals and genders, date/number formatting and parsing, and bidirectional text.)
name: workgroup_app
description: A flutter workgroup app.
publish_to: 'none'
version: 0.1.0
environment:
sdk: '>=2.18.2 <3.0.0'
dependencies:
flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
http: 0.13.0
intl: ^0.17.0 # DateFormat
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
[2] Prep Workgroup API
Laravel api/WorkgroupController.php:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Workgroup;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class WorkgroupController extends Controller
{
public function index()
{
$user = Auth::user();
$workgroups = Workgroup::where('mngr_email', $user->email)
->get();
return response()->json($workgroups);
}
public function store(Request $request)
{
try {
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'refn' => 'required|string',
'desn' => 'required|string',
'locn' => 'required|string',
'estartdate' => 'required|integer',
'key1' => 'required|string',
'key2' => 'required|string',
'key3' => 'required|string',
'key4' => 'required|string',
'key5' => 'required|string',
'n' => 'required|integer',
]);
$user = Auth::user();
$workgroup = Workgroup::create([
'mngr_email' => $user->email,
'name' => $validatedData['name'],
'refn' => $validatedData['refn'],
'desn' => $validatedData['desn'],
'locn' => $validatedData['locn'],
'estartdate' => $validatedData['estartdate'],
'key1' => $validatedData['key1'],
'key2' => $validatedData['key2'],
'key3' => $validatedData['key3'],
'key4' => $validatedData['key4'],
'key5' => $validatedData['key5'],
'admn' => 0,
'cord' => 0,
'oper' => 0,
'mngr' => $user->id,
'n' => $validatedData['n'],
]);
return response()->json($workgroup, 201);
} catch (\Illuminate\Validation\ValidationException $e) {
return response()->json($e->errors(), 422);
}
}
public function update(Request $request, $workgroup_id)
{
$workgroup = Workgroup::findOrFail($workgroup_id);
// Get the manager's email from the $workgroup
$managerEmail = $workgroup->mngr_email;
// Get the authenticated user's email
$authUserEmail = auth()->user()->email;
// Compare the manager's email with the authenticated user's email
if ($managerEmail === $authUserEmail) {
try {
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'refn' => 'required|string',
'desn' => 'required|string',
'locn' => 'required|string',
'estartdate' => 'required|integer',
'key1' => 'required|string',
'key2' => 'required|string',
'key3' => 'required|string',
'key4' => 'required|string',
'key5' => 'required|string',
'n' => 'required|integer',
]);
$workgroup->update($validatedData);
return response()->json($workgroup,200);
} catch (\Illuminate\Validation\ValidationException $e) {
return response()->json($e->errors(), 422);
}
}
else {
// The manager's email does not match the authenticated user's email
// You can return an error response or handle the situation accordingly
return response()->json(['error' => 'You are not authorized to update this workgroup.'], 403);
}
}
}
Laravel route api.php:
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
// Assigning middleware to individual route
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
// Assigning middleware to group of routes
use App\Http\Controllers\Api\WorkgroupController;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/workgroup', [WorkgroupController::class, 'index']);
Route::post('/workgroup', [WorkgroupController::class, 'store']);
Route::put('/workgroup/{workgroup_id}', [WorkgroupController::class, 'update']);
});
[3] Edit Workgroup Screens group
Workgroup Screens group consists of:
(1) Workgroups Screen - list of all workgroups.
(2) Workgroup Screen - editable form of an existing workgroup.
(3) Create Workgroup Screen - editable form for a new workgroup.
File-> lib/modules/workgroups.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'workgroupmeetings.dart';
class WorkgroupsScreen extends StatefulWidget {
const WorkgroupsScreen({super.key});
@override
_WorkgroupsScreenState createState() => _WorkgroupsScreenState();
}
class _WorkgroupsScreenState extends State<WorkgroupsScreen> {
List<dynamic> _workgroups = [];
final Box _boxUser = Hive.box("user");
@override
void initState() {
super.initState();
_fetchWorkgroups();
}
Future<void> _fetchWorkgroups() async {
const url = 'https://demo.razzi.my/spotnet/public/api/workgroup';
final authToken = await _boxUser.get('user_token');
try {
final response = await http.get(
Uri.parse(url),
headers: {
'Authorization': 'Bearer $authToken',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() {
_workgroups = data;
});
} else {
// Handle error
print('Error fetching workgroups: ${response.statusCode}');
}
} catch (e) {
// Handle network error
print('Error fetching workgroups: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Workgroups'),
actions: [
ElevatedButton(
onPressed: () {
// Navigate to the "Create Workgroup" screen
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CreateWorkgroupScreen())).then((_) {
// This block runs when you have come back to the 1st Page from 2nd.
_fetchWorkgroups();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromARGB(
255, 122, 192, 245), // Set the background color to red
foregroundColor: Colors.white, // Set the font color to white
padding:
const EdgeInsets.all(8.0), // Adjust the padding as needed
),
child: const Icon(Icons.add),
),
],
),
body: _workgroups.isEmpty
? const Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.builder(
itemCount: _workgroups.length,
itemBuilder: (context, index) {
final workgroup = _workgroups[index];
return ListTile(
leading: IconButton(
icon: const Icon(Icons.info),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
WorkgroupScreen(workgroup: workgroup)))
.then((_) {
// This block runs when you have come back to the 1st Page from 2nd.
_fetchWorkgroups();
});
},
),
title: Text('Name: ${workgroup['name']}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Reference: ${workgroup['refn']}'),
Text('Description: ${workgroup['desn']}'),
],
),
trailing: IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
WorkgroupMeetingsScreen(workgroup: workgroup),
),
).then((_) {
// This block runs when you have come back to the 1st Page from 2nd.
_fetchWorkgroups();
});
},
),
);
},
),
),
);
}
}
class WorkgroupScreen extends StatefulWidget {
final Map<String, dynamic> workgroup;
const WorkgroupScreen({super.key, required this.workgroup});
@override
_WorkgroupScreenState createState() => _WorkgroupScreenState();
}
class _WorkgroupScreenState extends State<WorkgroupScreen> {
final Box _boxUser = Hive.box("user");
late TextEditingController _nameController;
late TextEditingController _refnController;
late TextEditingController _desnController;
late TextEditingController _locnController;
late TextEditingController _startDateController;
late DateTime _dStartdate;
late TextEditingController _key1Controller;
late TextEditingController _key2Controller;
late TextEditingController _key3Controller;
late TextEditingController _key4Controller;
late TextEditingController _key5Controller;
late TextEditingController _nController;
bool _isEditing = false;
bool _isLoading = false;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.workgroup['name']);
_refnController = TextEditingController(text: widget.workgroup['refn']);
_desnController = TextEditingController(text: widget.workgroup['desn']);
_locnController = TextEditingController(text: widget.workgroup['locn']);
int _eStartdate = widget.workgroup['estartdate'] ?? 0; // parse epochdate
_dStartdate = DateTime.fromMillisecondsSinceEpoch(_eStartdate * 1000);
_startDateController = TextEditingController(
text: DateFormat('yyyy-MM-dd').format(_dStartdate));
_key1Controller = TextEditingController(text: widget.workgroup['key1']);
_key2Controller = TextEditingController(text: widget.workgroup['key2']);
_key3Controller = TextEditingController(text: widget.workgroup['key3']);
_key4Controller = TextEditingController(text: widget.workgroup['key4']);
_key5Controller = TextEditingController(text: widget.workgroup['key5']);
_nController =
TextEditingController(text: widget.workgroup['n']?.toString());
}
void _toggleEditMode() {
setState(() {
_isEditing = !_isEditing;
});
}
Future<void> _saveWorkgroup() async {
setState(() {
_isLoading = true;
});
try {
// Prepare the data to be sent
int n;
if (int.tryParse(_nController.text) != null) {
n = int.parse(_nController.text);
} else {
n = 0;
}
String sStartDate = _startDateController.text;
DateTime dStartDate = DateTime.parse(sStartDate);
int eStartDate = dStartDate.millisecondsSinceEpoch ~/ 1000;
final data = {
'refn': _refnController.text,
'desn': _desnController.text,
'locn': _locnController.text,
'estartdate': eStartDate,
'key1': _key1Controller.text,
'key2': _key2Controller.text,
'key3': _key3Controller.text,
'key4': _key4Controller.text,
'key5': _key5Controller.text,
'name': _nameController.text,
'n': n,
};
final workgroupId = widget.workgroup['id'];
final url =
'https://demo.razzi.my/spotnet/public/api/workgroup/$workgroupId';
final authToken = await _boxUser.get('user_token');
// Send the data to the API endpoint
final response = await http.put(
Uri.parse(url),
body: jsonEncode(data),
headers: {
'Authorization': 'Bearer $authToken',
'Content-Type': 'application/json',
},
);
// Check the response status code
if (response.statusCode == 200) {
// Workgroup saved successfully
print('Workgroup saved successfully');
Navigator.of(context).pop();
} else {
// Error saving the workgroup
print(
'Error saving workgroup: ${response.statusCode} - ${response.body}');
}
} catch (e) {
// Handle any exceptions that occur during the API call
print('Error saving workgroup: $e');
}
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Stack(children: [
Scaffold(
appBar: AppBar(
title: const Text('Workgroup Details'),
actions: [
if (_isEditing)
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveWorkgroup,
),
IconButton(
icon: Icon(_isEditing ? Icons.cancel : Icons.edit),
onPressed: _toggleEditMode,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Name',
),
),
TextField(
controller: _refnController,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Reference',
),
),
TextField(
controller: _desnController,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Description',
),
),
TextField(
controller: _locnController,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Location',
),
),
TextFormField(
controller: _startDateController,
enabled: _isEditing,
readOnly: true,
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: _dStartdate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
setState(() {
_startDateController.text =
DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
},
decoration: const InputDecoration(
labelText: 'Start Date',
hintText: 'Select start date',
),
),
TextField(
controller: _key1Controller,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Key 1',
),
),
TextField(
controller: _key2Controller,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Key 2',
),
),
TextField(
controller: _key3Controller,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Key 3',
),
),
TextField(
controller: _key4Controller,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Key 4',
),
),
TextField(
controller: _key5Controller,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'Key 5',
),
),
TextField(
controller: _nController,
enabled: _isEditing,
decoration: const InputDecoration(
labelText: 'N',
),
),
],
),
),
),
),
if (_isLoading)
Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child: CircularProgressIndicator(),
),
),
]);
}
}
class CreateWorkgroupScreen extends StatefulWidget {
@override
_CreateWorkgroupScreenState createState() => _CreateWorkgroupScreenState();
}
class _CreateWorkgroupScreenState extends State<CreateWorkgroupScreen> {
final Box _boxUser = Hive.box("user");
late TextEditingController _nameController;
late TextEditingController _refnController;
late TextEditingController _desnController;
late TextEditingController _locnController;
late TextEditingController _startDateController;
late TextEditingController _key1Controller;
late TextEditingController _key2Controller;
late TextEditingController _key3Controller;
late TextEditingController _key4Controller;
late TextEditingController _key5Controller;
late TextEditingController _nController;
bool _isLoading = false;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_refnController = TextEditingController();
_desnController = TextEditingController();
_locnController = TextEditingController();
_startDateController = TextEditingController(
text: DateFormat('yyyy-MM-dd').format(DateTime.now()));
_key1Controller = TextEditingController(text: '-');
_key2Controller = TextEditingController(text: '-');
_key3Controller = TextEditingController(text: '-');
_key4Controller = TextEditingController(text: '-');
_key5Controller = TextEditingController(text: '-');
_nController = TextEditingController(text: (0).toString());
}
Future<void> _createWorkgroup() async {
setState(() {
_isLoading = true;
});
try {
// Prepare the data to be sent
int n;
if (int.tryParse(_nController.text) != null) {
n = int.parse(_nController.text);
} else {
n = 0;
}
String sStartDate = _startDateController.text;
DateTime dStartDate = DateTime.parse(sStartDate);
int eStartDate = dStartDate.millisecondsSinceEpoch ~/ 1000;
final data = {
'refn': _refnController.text,
'desn': _desnController.text,
'locn': _locnController.text,
'estartdate': eStartDate,
'key1': _key1Controller.text,
'key2': _key2Controller.text,
'key3': _key3Controller.text,
'key4': _key4Controller.text,
'key5': _key5Controller.text,
'name': _nameController.text,
'n': n,
};
final url = 'https://demo.razzi.my/spotnet/public/api/workgroup';
final authToken = await _boxUser.get('user_token');
// Send the data to the API endpoint
final response = await http.post(
Uri.parse(url),
body: jsonEncode(data),
headers: {
'Authorization': 'Bearer $authToken',
'Content-Type': 'application/json',
},
);
// Check the response status code
if (response.statusCode == 201) {
// Workgroup created successfully
print('Workgroup created successfully');
Navigator.of(context).pop();
} else {
// Error creating the workgroup
print(
'Error creating workgroup: ${response.statusCode} - ${response.body}');
}
} catch (e) {
// Handle any exceptions that occur during the API call
print('Error creating workgroup: $e');
}
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Stack(children: [
Scaffold(
appBar: AppBar(
title: const Text('Create Workgroup'),
actions: [
IconButton(
icon: const Icon(Icons.save),
onPressed: _createWorkgroup,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
),
),
TextField(
controller: _refnController,
decoration: const InputDecoration(
labelText: 'Reference',
),
),
TextField(
controller: _desnController,
decoration: const InputDecoration(
labelText: 'Description',
),
),
TextField(
controller: _locnController,
decoration: const InputDecoration(
labelText: 'Location',
),
),
TextFormField(
controller: _startDateController,
readOnly: true,
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
setState(() {
_startDateController.text =
DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
},
decoration: const InputDecoration(
labelText: 'Start Date',
hintText: 'Select start date',
),
),
TextField(
controller: _key1Controller,
decoration: const InputDecoration(
labelText: 'Key 1',
),
),
TextField(
controller: _key2Controller,
decoration: const InputDecoration(
labelText: 'Key 2',
),
),
TextField(
controller: _key3Controller,
decoration: const InputDecoration(
labelText: 'Key 3',
),
),
TextField(
controller: _key4Controller,
decoration: const InputDecoration(
labelText: 'Key 4',
),
),
TextField(
controller: _key5Controller,
decoration: const InputDecoration(
labelText: 'Key 5',
),
),
TextField(
controller: _nController,
decoration: const InputDecoration(
labelText: 'N',
),
),
],
),
),
),
),
if (_isLoading)
Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child: CircularProgressIndicator(),
),
),
]);
}
}
Output for WorkgroupsScreen:
Output for CreateWorkgroupScreen:
Output for WorkgroupScreen:
WorkgroupScreen in Edit mode:
[4] Create a draft of Workgroup Meetings Screen
File-> lib/modules/workgroupmeetings.dart
import 'package:flutter/material.dart';
class WorkgroupMeetingsScreen extends StatefulWidget {
final Map<String, dynamic> workgroup;
const WorkgroupMeetingsScreen({super.key, required this.workgroup});
@override
_WorkgroupMeetingsScreenState createState() => _WorkgroupMeetingsScreenState();
}
class _WorkgroupMeetingsScreenState extends State<WorkgroupMeetingsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Workgroup Meetings - ${widget.workgroup['name']}'),
),
body: Center(
child: Text('Workgroup Meetings for ${widget.workgroup['name']}'),
),
);
}
}
Output for WorkgroupMeetingsScreen: