htlib 0.1.0
htlib: ^0.1.0 copied to clipboard
A versatile and lightweight Flutter toolkit for state management, shell routing, dependency injection, responsive UI, MVVM, and form validation to speed up development.
htlib #
A versatile and lightweight toolkit for Flutter, providing a collection of essential utilities to accelerate your development process. htlib
bundles common patterns like state management, routing, dependency injection, and responsive UI into a single, cohesive package.
Features #
- Reactive State Management: A simple, powerful, and fine-grained reactivity system inspired by MobX/Vue, featuring
Observable
,Computed
, andObserver
widgets. - Declarative Shell-based Routing: A robust routing solution that makes nested navigation (e.g., with a
BottomNavigationBar
) intuitive and easy to manage usingShellRouter
andShellOutlet
. - Simple Dependency Injection: A lightweight service locator for managing your application's dependencies with singleton and factory patterns.
- Responsive UI Toolkit: A set of widgets and extensions (
ResponsiveGrid
,ResponsiveWidget
) to easily build adaptive layouts for mobile, tablet, and desktop. - MVVM Architecture: A basic implementation of the Model-View-ViewModel pattern (
ViewModel
,ViewWidget
) to help structure your UI logic. - Form Validation: A chainable and extensible validator system for your form fields.
- Dynamic Page Title: A widget to easily manage the app's title in the OS task switcher for web and desktop.
Installation #
Add htlib
to your pubspec.yaml
file:
flutter pub add htlib
Then, run flutter pub get
in your terminal.
Usage #
1. Reactive State Management #
Create reactive state that automatically updates your UI when it changes.
Observable<T>
: A container for a value that can be observed.Computed<T>
: A value derived from other observables. It's cached and only re-evaluates when its dependencies change.Observer
: A widget that rebuilds automatically when any observables it uses are modified.ObservableArray<T>
: A reactive list that notifies listeners when its contents are modified.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
// 1. Create your observables
final counter = Observable(0);
final isEven = Computed(() => counter.value.isEven);
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// 3. Use Observer to rebuild the UI on state changes
child: Observer(
builder: (_) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: ${counter.value}'), // or just Text('Count: $counter')
Text('Is Even: ${isEven.value}'), // or just Text('Is Even: $isEven')
],
);
},
),
),
floatingActionButton: FloatingActionButton(
// 2. Modify the state, and the UI will update automatically
onPressed: () => counter.value++,
child: Icon(Icons.add),
),
);
}
}
2. Dependency Injection (Injection
) #
A simple service locator to register and retrieve your application's services.
Example:
import 'package:htlib/htlib.dart';
// 1. Define your services
class ApiService {
Future<String> fetchData() async => 'Hello from API';
}
class AuthRepository {
final ApiService _api;
AuthRepository(this._api);
}
// 2. Create an injection instance
final injection = Injection();
void setupDependencies() {
// 3. Register dependencies
// Singleton: one instance throughout the app's lifecycle
injection.addSingleton<ApiService>(() => ApiService());
// Factory: a new instance is created on every request
injection.addFactory<AuthRepository>(() => AuthRepository(injection.get<ApiService>()));
}
void main() {
setupDependencies();
// 4. Retrieve dependencies
final api = injection.get<ApiService>();
// Or using the shorthand:
final authRepo = injection<AuthRepository>();
print(api);
print(authRepo);
}
3. Declarative Routing (ShellRouter
) #
Manage nested navigation (e.g., Scaffold
with a BottomNavigationBar
) using a shell-based approach. The ShellOutlet
widget acts as a placeholder for child routes.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
// 1. Define your routes
final router = ShellRouter(
// The 'container' is the shell that wraps your child pages
container: (context) => AppShell(),
routes: [
ShellRoute(
path: 'a',
builder: (context, params) => const Center(child: Text('Page A')),
),
ShellRoute(
path: 'b',
// This route has a nested child route
builder: (context, params) => const ShellOutlet(), // Use ShellOutlet for nested routes
children: [
ShellRoute(
path: ':id',
builder: (context, params) =>
Center(child: Text('Detail for B with ID: ${params['id']}'))),
],
),
],
);
void main() => runApp(MaterialApp.router(routerConfig: router));
// 2. Create your Shell UI with a ShellOutlet
class AppShell extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Shell Router Example")),
// The ShellOutlet renders the matched child route's widget here
body: const ShellOutlet(),
bottomNavigationBar: BottomNavigationBar(
// Use locationOf to update UI based on the current route
currentIndex: ShellRouter.locationOf(context).startsWith('/b') ? 1 : 0,
onTap: (index) {
if (index == 0) router.navigate(Uri.parse('/a'));
if (index == 1) router.navigate(Uri.parse('/b/123')); // Navigate to a nested route
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Page A'),
BottomNavigationBarItem(icon: Icon(Icons.business), label: 'Page B'),
],
),
);
}
}
4. Responsive UI Toolkit #
Build adaptive UIs with a powerful 12-column grid system and responsive helpers.
context.responsive<T>()
: Select a value based on screen size (mobile
,tablet
,desktop
).ResponsiveWidget
: Select a widget builder based on screen size.ResponsiveGrid
: Arranges children in a responsive 12-column layout.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
class ResponsiveScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Example 1: Get a responsive value
final double padding = context.responsive<double>(
mobile: 8.0,
tablet: 16.0,
desktop: 24.0,
);
// Example 2: Use the ResponsiveGrid
return Scaffold(
appBar: AppBar(title: Text('Responsive Grid')),
body: SingleChildScrollView(
padding: EdgeInsets.all(padding),
child: ResponsiveGrid(
columnSpacing: 12,
rowSpacing: 12,
children: [
ResponsiveItem.span(
child: Container(color: Colors.red, height: 100),
mobile: 12, // Full width on mobile
tablet: 6, // Half width on tablet
desktop: 4, // One-third width on desktop
),
ResponsiveItem.span(
child: Container(color: Colors.green, height: 100),
mobile: 12,
tablet: 6,
desktop: 4,
),
ResponsiveItem.span(
child: Container(color: Colors.blue, height: 100),
mobile: 12,
tablet: 12,
desktop: 4,
),
],
),
),
);
}
}
5. MVVM Pattern (ViewModel
& ViewWidget
) #
Separate your UI from your business logic using the Model-View-ViewModel pattern. The ViewModel
holds the state and logic, and updateView()
triggers a rebuild of the ViewWidget
.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
// 1. Define your ViewModel
class CounterViewModel extends ViewModel {
int _count = 0;
int get count => _count;
void increment() {
_count++;
updateView(); // This will trigger a rebuild of the view
}
}
// 2. Create your ViewWidget, which connects the ViewModel to the UI
class CounterView extends ViewWidget<CounterViewModel> {
const CounterView({super.key});
@override
CounterViewModel createModel() => CounterViewModel();
@override
Widget build(BuildContext context, CounterViewModel model) {
return Scaffold(
body: Center(
child: Text('Count: ${model.count}'),
),
floatingActionButton: FloatingActionButton(
onPressed: model.increment,
child: const Icon(Icons.add),
),
);
}
}
6. Form Validation #
Chain multiple validation rules easily for TextFormField
and other form inputs.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
class MyForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Form(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextFormField(
decoration: InputDecoration(labelText: 'Email'),
autovalidateMode: AutovalidateMode.onUserInteraction,
// Chain multiple validators using Validator.rules
validator: Validator.rules([
Required(message: 'Please enter an email'),
Email(message: 'Please enter a valid email'),
]),
),
),
);
}
}
7. Page Title Management #
Easily set the application's title in the OS task manager on desktop and web builds. It intelligently handles nested PageTitle
widgets.
Example:
import 'package:flutter/material.dart';
import 'package:htlib/htlib.dart';
void main() {
// Set a global app name to be appended to all titles (optional)
PageTitle.appName = 'My Awesome App';
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// This will set the window title to "Home | My Awesome App"
return PageTitle(
title: 'Home',
child: Scaffold(
appBar: AppBar(title: Text('Home Screen')),
body: Center(child: Text('Welcome!')),
),
);
}
}