utills 2.0.1
utills: ^2.0.1 copied to clipboard
A comprehensive Flutter utility package providing layout gaps, form validators, a generic API handler, and a scroll-ready paginator.
How to use utills #
This example shows the common setup for the latest utills APIs, including animated toast notifications and navigation helpers.
Install #
dependencies:
utills: ^2.0.1
Import #
import 'package:flutter/material.dart';
import 'package:utills/utills.dart';
CustomToast setup #
Create one navigator key, pass it to CustomToast.init(), and use the same key in MaterialApp.
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() {
CustomToast.init(
navigatorKey: navigatorKey,
config: CustomToastConfig(
duration: const Duration(seconds: 2),
position: ToastPosition.top,
// Add backgroundColors, textStyles, margin, and radius as needed.
),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
/// mention the key here
navigatorKey: navigatorKey,
home: const HomeScreen(),
);
}
}
Show toast messages from anywhere after initialization.
CustomToast.show(
'Data saved successfully!',
type: ToastType.success,
);
CustomToast.show(
'Failed to connect to server.',
type: ToastType.error,
);
CustomToast.show(
'Your subscription expires soon!',
type: ToastType.warning,
);
Navigation helper #
Use Navigate to reduce route boilerplate. Navigate.push() also supports slide direction and duration.
Navigate.push(context, const ProfileScreen());
Navigate.push(
context,
const DetailsScreen(),
direction: NavDirection.bottomToUp,
duration: const Duration(milliseconds: 400),
);
Navigate.pushReplacement(context, const HomeScreen());
Navigate.pushAndRemoveUntil(context, const WelcomeScreen());
Navigate.pop(context);
Attempt and failure handling #
Use Attempt<T> when a function can return either data or a typed Failure. This example uses the http package for the network call.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:utills/utills.dart';
Future<Attempt<String>> exampleApiFunction({
required Map<String, String> header,
}) async {
final url = Uri.parse('$baseUrl/example/$id/action');
try {
final response = await http
.post(url, headers: header)
.timeout(const Duration(seconds: 10));
final data = jsonDecode(response.body);
if (response.statusCode == 200) {
Log.success(data);
return success('successfully completed');
} else {
return failed(
mapStatusCodeToFailure(
response.statusCode,
message: data['detail']?.toString(),
),
);
}
} on TimeoutException catch (e) {
Log.warning('Request timed out: $e');
return failed(const TimeoutFailure());
} on SocketException catch (e) {
Log.warning('No Internet or Server unreachable: $e');
return failed(const InternetFailure());
} catch (e) {
Log.error('Error : $e');
return failed(const UnknownFailure());
}
}
From the UI side, destructure the record returned by the function.
Future<void> exampleUiFunction() async {
final (data, error) = await exampleApiFunction(
header: {'Authorization': 'Bearer token'},
);
if (data != null) {
CustomToast.showAnimation(data, type: ToastType.success);
} else {
CustomToast.showAnimation(
error?.description ?? 'Something went wrong',
type: ToastType.error,
);
}
}
Paginator #
RestPaginator<T> loads pages, stores the loaded items, and notifies listeners when its state changes.
class ExampleController extends ChangeNotifier {
ExampleController() {
paginator.addListener(notifyListeners);
}
late final RestPaginator<Map<String, dynamic>> paginator =
RestPaginator<Map<String, dynamic>>(
pageSize: 20,
getPage: fetchExamplePage,
);
Future<RestPageData<Map<String, dynamic>>?> fetchExamplePage(
int page,
int pageSize,
) async {
final response = await http.get(
Uri.parse('https://api.example.com/items?page=$page&pageSize=$pageSize'),
);
if (response.statusCode != 200) return null;
final body = jsonDecode(response.body) as Map<String, dynamic>;
final items = (body['data'] as List).cast<Map<String, dynamic>>();
final totalPages = body['totalPages'] as int;
return (items: items, totalPages: totalPages);
}
@override
void dispose() {
paginator.removeListener(notifyListeners);
paginator.dispose();
super.dispose();
}
}
Use getItem(index) in a list. It automatically asks for the next page when the user gets near the end.
ListView.builder(
itemCount: controller.paginator.availableItemCount +
(controller.paginator.endReached ? 0 : 1),
itemBuilder: (context, index) {
if (index >= controller.paginator.availableItemCount) {
return const Center(child: CircularProgressIndicator());
}
final item = controller.paginator.getItem(index);
return ListTile(
title: Text(item['title'] ?? ''),
);
},
);
Debugger #
Use Log for colorized debug-only output. These helpers are no-ops in release builds.
Log.info('Loading items');
Log.success({'status': 'completed', 'count': 20});
Log.warning('Token will expire soon');
Log.error('Request failed');
Log.trace('exampleApiFunction() called');
Other utilities #
// Gaps
Column(
children: [
const Text('Title'),
vPad10,
const Text('Subtitle'),
],
);
// Validators
TextFormField(
validator: CommonValidator.emailValidator,
);