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.

Table of Contents

Features

  • 📱 Responsive UI: Easily create responsive layouts that adapt to different screen sizes and orientations
  • 📦 Custom Sized Box: Convenient extensions for creating SizedBox widgets
  • 🌐 Internet Connectivity: Built-in connectivity monitoring with customizable no-internet UI
  • 💱 Currency Converter: Live exchange rates and currency conversion utilities
  • 📦 Package Management: Manage dependencies with ease

Installation

dependencies:
  fittor: ^latest_version
flutter pub add fittor

Then run:

flutter pub get

Usage

Responsive

Fittor provides a responsive design system through mixins and extensions. Here's how to use it:

Basic Setup

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(),
    );
  }
}

Using in Widgets

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
  ),
)

Responsive Mixins

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
      ),
    );
  }
}

Custom Sized Box

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

Internet Connectivity

Fittor includes built-in internet connectivity monitoring without any external packages.

Using ConnectivityWrapper

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.

Using ConnectivityMixin

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();
    }
  }
}

Currency Converter

Fittor provides a currency converter utility with live exchange rates.

Basic Conversion

// 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

// 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 with Custom Symbols

// Format a currency amount with proper symbol
String formatted = context.formatCurrency(1234.56, 'USD');
print(formatted); // Outputs: $1,234.56

Live Exchange Rates

// Get the current exchange rate between two currencies
double rate = await context.getExchangeRate('INR', 'USD');
print('1 INR = $rate USD'); 

Advanced Usage

Direct Access to CurrencyConverter

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']}');

FittorCurrency Utilities

final currencyUtils = FittorCurrency();

// Create a currency text widget
Widget priceText = currencyUtils.currencyText(
  '\$1,234.56',
  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
);

State Management

Overview

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.

Features

  • Controller-based state management: Create reactive UIs with minimal code
  • Dependency injection: Easily register and find controllers throughout your app
  • Reactive value wrappers: Optimized UI updates with fine-grained reactivity
  • Bindings system: Organize dependencies by route or feature
  • Extension methods: Access controllers directly from BuildContext
  • Auto-disposal: Controllers are automatically managed in the widget lifecycle

Getting Started

1. Initialize Fittor

Initialize Fittor at the root of your application:

void main() {
  runApp(
    FitInitializer(
      child: MyApp(),
      initialBindings: [AppBindings()],
    ),
  );
}

2. Create a Controller

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();
  }
}

3. Using Reactive Values

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;
  }
}

4. Register Controllers with Bindings

Create a bindings class to organize your dependencies:

class AppBindings extends FitBindings {
  @override
  void dependencies() {
    lazyPut(() => CounterController());
    lazyPut(() => UserController());
  }
}

5. Using Controllers in Widgets

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');
  },
)

6. Access Controllers via BuildContext Extension

final controller = context.find<CounterController>();
controller.increment();

Core Concepts

Controllers

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'
  }
}

Dependency Injection

Fittor provides several methods to register and find controllers:

  • Fit.lazyPut<T>() : Registers a controller for lazy initialization
  • Fit.put<T>() : Registers an already initialized controller
  • Fit.find<T>() : Finds a registered controller
  • Fit.delete<T>() : Removes a controller and calls its onDelete method

Advanced Usage Controller

Tagging Controllers

Register 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');

Auto-registration with FitBuilder

Controllers can be automatically registered with FitBuilder:

FitBuilder<DashboardController>(
  controller: DashboardController(),
  autoRegister: true,
  builder: (context, controller) {
    return DashboardView(controller: controller);
  },
)

Initialization and Disposal

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);
  },
)

Fittor Blur Ash

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,
})

Usage Example Using Cache Network Image


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,
),

Read more

Features

  • Trim by length or lines: Choose between character count or line-based trimming
  • Rich text support: Use FitReadMore.rich() for complex text formatting
  • Text annotations: Support for URLs, hashtags, mentions, and custom patterns
  • Customizable styling: Full control over colors, fonts, and text styles
  • External control: Control expand/collapse state externally
  • Callbacks: Get notified when text is expanded or collapsed
  • Accessibility: Full support for text selection and screen readers
  • Pre/post text: Add content before and after the main text
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

Responsive Features

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

Connectivity Features

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 */ }

Currency Features

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');

🐞 Troubleshooting

  • Ensure the package is correctly imported
  • Check that you're using the latest version
  • Verify flutter and dart SDK compatibility
  • Check for any conflicts with other packages

📞 Contact & Support

Author: Mushthak VP

🌐 Connect With Me

💡 Collaboration

Have a project or need custom Flutter development? Feel free to reach out! I'm always open to interesting projects, collaborations, and opportunities.

🤝 Contributing

Contributions are welcome! Whether you're reporting bugs, suggesting improvements, or want to collaborate, don't hesitate to connect.

License

MIT - Copyright © 2025 Mushthak VP

🆘 Support

For any questions, issues, or custom development needs, please contact me directly via email or social media channels.