fittor
Ein Flutter-Paket für responsive Benutzeroberflächen und Zustandsverwaltung. Passt sich Bildschirmgrößen und Orientierungen an.
Ein umfassendes Flutter-Paket zur Erstellung von responsiven Benutzeroberflächen, die sich an verschiedene Bildschirmgrößen und Ausrichtungen anpassen.
{"sdk":"flutter"}^1.0.0{"sdk":"flutter"}^3.0.0Englischer Projektschnappschuss. Aktuelle Inhalte auf GitHub.
Fittor Logo
pub package pub points License: MIT GitHub issues GitHub stars
A comprehensive Flutter package for responsive UI design and network connectivity management. A lightweight, intuitive state management solution for Flutter applications.
dependencies:
fittor: ^latest_version
flutter pub add fittor
Then run:
flutter pub get
Fittor provides a responsive design system through mixins and extensions. Here's how to use it:
Add the FittorAppMixin to your app:
class MyApp extends StatelessWidget with FittorAppMixin {
const MyApp({super.key});
@override
Widget responsive(BuildContext context) {
return MaterialApp(
title: 'Responsive Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomeScreen(),
);
}
}
Access responsive values through context extensions:
Container(
width: context.wp(50), // 50% of screen width
height: context.hp(25), // 25% of screen height
padding: EdgeInsets.all(context.p16), // Adaptive padding
child: Text(
'Responsive Text',
style: TextStyle(fontSize: context.fs18), // Adaptive font size
),
)
Use the FittorMixin in your StatefulWidget:
class _MyWidgetState extends State<MyWidget> with FittorMixin {
@override
Widget build(BuildContext context) {
return Container(
width: wp(50), // 50% of screen width
padding: EdgeInsets.all(p16), // Predefined padding
child: Text(
'Hello World',
style: TextStyle(fontSize: fs(18)), // Responsive font size
),
);
}
}
Create SizedBox widgets with simple extensions:
// Width SizedBox
20.w // SizedBox with width 20
// Height SizedBox
16.h // SizedBox with height 16
// Square SizedBox
24.s // SizedBox with width and height both 24
Fittor includes built-in internet connectivity monitoring without any external packages.
class MyApp extends StatelessWidget with FittorAppMixin {
const MyApp({super.key});
@override
Widget responsive(BuildContext context) {
return MaterialApp(
home: ConnectivityWrapper(
ignoreOfflineState: true,
onConnectivityChanged: (status) {
debugPrint('Connectivity status: $status');
},
child: const HomeScreen(),
),
);
}
}
Wrap your widget with ConnectivityWrapper to automatically show a no-internet screen when connectivity is lost:
ConnectivityWrapper(
child: YourWidget(),
// Optional customizations:
offlineWidget: YourCustomOfflineWidget(),
onConnectivityChanged: (status) {
print('Connectivity status: $status');
},
)
The ignoreOfflineState parameter (default: false) controls whether the wrapper automatically shows the no-internet screen:
ConnectivityWrapper(
ignoreOfflineState: true, // Don't show no-internet screen automatically
onConnectivityChanged: (status) {
// Handle connectivity changes yourself
},
child: YourWidget(),
)
When ignoreOfflineState is set to true, the ConnectivityWrapper will not automatically show the no-internet screen when connectivity is lost. Instead, it will continue showing your child widget and notify you of connectivity changes through the onConnectivityChanged callback. This is useful when you want to handle connectivity UI yourself, such as showing snackbars or banners instead of full-screen notifications.
For more fine-grained control, use the ConnectivityMixin in your StatefulWidget:
class _MyScreenState extends State<MyScreen> with ConnectivityMixin {
@override
void onConnectivityChanged(ConnectivityStatus status) {
if (status == ConnectivityStatus.online) {
// Handle online state
} else {
// Handle offline state
}
}
@override
Widget build(BuildContext context) {
// Access connectivity status with:
if (isOnline) {
return OnlineContent();
} else {
return OfflineContent();
}
}
}
Fittor provides a currency converter utility with live exchange rates.
// Convert 100 INR to USD
double usdAmount = await context.convertCurrency(
from: 'INR',
to: 'USD',
amount: 100.0,
);
print('100 INR = $usdAmount USD');
// Convert and format with currency symbol
String formattedAmount = await context.convertAndFormat(
from: 'INR',
to: 'USD',
amount: 100.0,
);
print('100 INR = $formattedAmount'); // Outputs: 100 INR = $1.17
// Format a currency amount with proper symbol
String formatted = context.formatCurrency(1234.56, 'USD');
print(formatted); // Outputs: $1,234.56
// Get the current exchange rate between two currencies
double rate = await context.getExchangeRate('INR', 'USD');
print('1 INR = $rate USD');
final converter = CurrencyConverter();
// Manually get latest rates for a base currency
Map<String, dynamic> rates = await converter.getLatestRates('EUR');
// Check cache status
Map<String, dynamic> cacheInfo = converter.getCacheInfo();
print('Last updated: ${cacheInfo['lastUpdated']}');
final currencyUtils = FittorCurrency();
// Create a currency text widget
Widget priceText = currencyUtils.currencyText(
'\$1,234.56',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
);
Fittor is a pragmatic state management library designed to make Flutter development more efficient with minimal boilerplate. It offers a controller-based approach, more focused API.
Initialize Fittor at the root of your application:
void main() {
runApp(
FitInitializer(
child: MyApp(),
initialBindings: [AppBindings()],
),
);
}
Controllers manage your application state and business logic:
class CounterController extends FitController {
int count = 0;
void increment() {
count++;
fittor(); // Notify listeners to rebuild
}
@override
void onDelete() {
// Clean up resources when controller is removed
super.onDelete();
}
}
For more granular updates, use the FitValue class:
class UserController extends FitController {
final username = "".fit; // Creates a FitValue<String>
final isLoggedIn = false.fit; // Creates a FitValue<bool>
void login(String name) {
username.val = name; // This will automatically update listeners
isLoggedIn.val = true;
}
}
Create a bindings class to organize your dependencies:
class AppBindings extends FitBindings {
@override
void dependencies() {
lazyPut(() => CounterController());
lazyPut(() => UserController());
}
}
Access your controllers in the UI using FitBuilder:
FitBuilder<CounterController>(
controller: Fit.find<CounterController>(),
builder: (context, controller) {
return Text('Count: ${controller.count}');
},
)
For reactive values:
FitValueBuilder<String>(
fitValue: userController.username,
builder: (context, username) {
return Text('Hello, $username');
},
)
final controller = context.find<CounterController>();
controller.increment();
Controllers are the heart of your application logic. Extend FitController to create a controller:
class ThemeController extends FitController {
bool isDarkMode = false;
void toggleTheme() {
isDarkMode = !isDarkMode;
fittor(); // Notify all listeners (will rebuild UI)
}
// To update specific widgets only
void updateSpecificWidgets() {
fittor('theme-tag'); // Only rebuilds widgets with 'theme-tag'
}
}
Fittor provides several methods to register and find controllers:
Fit.lazyPut<T>() : Registers a controller for lazy initializationFit.put<T>() : Registers an already initialized controllerFit.find<T>() : Finds a registered controllerFit.delete<T>() : Removes a controller and calls its onDelete methodRegister multiple instances of the same controller type:
// Registration
Fit.put<ApiClient>(ProductApiClient(), tag: 'product');
Fit.put<ApiClient>(UserApiClient(), tag: 'user');
// Usage
final productApi = Fit.find<ApiClient>(tag: 'product');
final userApi = Fit.find<ApiClient>(tag: 'user');
Controllers can be automatically registered with FitBuilder:
FitBuilder<DashboardController>(
controller: DashboardController(),
autoRegister: true,
builder: (context, controller) {
return DashboardView(controller: controller);
},
)
Controllers are automatically disposed when their widgets are removed from the tree. You can also manually dispose controllers:
FitBuilder<VideoController>(
controller: Fit.find<VideoController>(),
init: (controller) => controller.initialize(),
dispose: (controller) => controller.cleanup(),
builder: (context, controller) {
return VideoPlayer(controller);
},
)
You can now use the FittorBlurAsh widget to add a blur effect to a widget.
// Usage
FittorBlurAsh FittorBlurAsh({
Key? key,
required String hash,
double? width,
double? height,
BoxFit fit = BoxFit.cover,
Color? color,
Widget? child,
Widget? loadingWidget,
Widget? errorWidget,
int resolution = 32,
})
CachedNetworkImage(
imageUrl: 'https://images.unsplash.com/photo-1506744038136-46273834b3fb',
placeholder: (context, url) {
return FittorBlurAsh(
hash: 'UbCP*BWYWWof~qWraykC_3WYjZof?bflaxoL',
width: 300,
height: 200,
);
},
errorWidget: (context, url, error) {
return FittorBlurAsh(
hash: 'UbCP*BWYWWof~qWraykC_3WYjZof?bflaxoL',
width: 300,
height: 200,
);
},
width: 300,
height: 200,
fit: BoxFit.cover,
),
Features
FitReadMore(
'Your very long text content goes here...',
trimLength: 150,
trimCollapsedText: 'Read more',
trimExpandedText: 'Show less',
colorClickableText: Colors.blue,
)
FitReadMore(
'Check out https://example.com and follow @username #flutter',
trimLength: 100,
annotations: [
// URL annotation
FitAnnotation(
regExp: RegExp(r'https?://[^\s]+'),
spanBuilder: ({required text, required textStyle}) => TextSpan(
text: text,
style: textStyle.copyWith(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
),
// Hashtag annotation
FitAnnotation(
regExp: RegExp(r'#\w+'),
spanBuilder: ({required text, required textStyle}) => TextSpan(
text: text,
style: textStyle.copyWith(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
),
// Mention annotation
FitAnnotation(
regExp: RegExp(r'@\w+'),
spanBuilder: ({required text, required textStyle}) => TextSpan(
text: text,
style: textStyle.copyWith(
color: Colors.purple,
fontWeight: FontWeight.bold,
),
),
),
],
)
| Extension | Description | Example |
|---|---|---|
num.w |
Creates SizedBox with width | 20.w creates SizedBox(width: 20) |
num.h |
Creates SizedBox with height | 16.h creates SizedBox(height: 16) |
num.s |
Creates square SizedBox | 24.s creates SizedBox.square(dimension: 24) |
context.wp(%) |
Percentage of screen width | context.wp(80) gives 80% of screen width |
context.hp(%) |
Percentage of screen height | context.hp(50) gives 50% of screen height |
context.p* |
Adaptive padding | context.p16 gives adaptive 16 padding |
context.fs* |
Adaptive font size | context.fs16 returns responsive font size 16 |
| Feature | Description | Example |
|---|---|---|
ConnectivityWrapper |
Wraps UI with connectivity monitoring | ConnectivityWrapper(child: MyApp()) |
ConnectivityMixin |
Mixin for StatefulWidgets | class _MyState extends State<MyWidget> with ConnectivityMixin |
isOnline property |
Check online status with mixin | if (isOnline) { /* do network request */ } |
checkConnectivity() |
Manual connectivity check | await checkConnectivity() |
onConnectivityChanged |
Handle status changes | onConnectivityChanged(status) { /* handle change */ } |
| Feature | Description | Example |
|---|---|---|
convertCurrency() |
Convert currency | double usdAmount = await context.convertCurrency(from: 'INR', to: 'USD', amount: 100.0); |
convertAndFormat() |
Convert and format | String formatted = await context.convertAndFormat(from: 'INR', to: 'USD', amount: 100.0); |
formatCurrency() |
Format currency | String formatted = context.formatCurrency(1234.56, 'USD'); |
getExchange Rate() |
Get exchange rate | double rate = await context.getExchangeRate('INR', 'USD'); |
Author: Mushthak VP
Have a project or need custom Flutter development? Feel free to reach out! I'm always open to interesting projects, collaborations, and opportunities.
Contributions are welcome! Whether you're reporting bugs, suggesting improvements, or want to collaborate, don't hesitate to connect.
MIT - Copyright © 2025 Mushthak VP
For any questions, issues, or custom development needs, please contact me directly via email or social media channels.