Skip to main content

50 posts tagged with "Flutter"

View All Tags

Implementing Custom Error Boundaries for Robust Flutter UI Failures

Published: · 5 min read
Sandra Rosa Antony
Software Engineer, Appxiom

In mobile engineering, application reliability is more than just a buzzword-it's a non-negotiable expectation for users and businesses. When a Flutter app faces an unexpected UI failure, leaving users stranded with a blank screen or a hard crash damages trust and complicates both debugging and observability. To build truly robust Flutter apps, it's critical to capture, contain, and report these failures gracefully. This post dives deep into implementing custom error boundaries in Flutter, focusing on real-world engineering challenges around performance, debugging, observability, and reliability.


Why UI Failures Are a Real-World Challenge

Although Flutter provides a global FlutterError.onError handler and general crash reporting options, many production bugs are:

  • Component-specific and intermittent: UI crashes triggered by edge case state or data inconsistencies.
  • Hard to reproduce: Failures in a specific widget tree context or caused by rare user behavior.
  • Invisible until too late: Resulting in a bad user experience, with little feedback or in-app traceability.

These issues underline the need for component-scoped error boundaries-an established pattern in web frameworks like React, but not natively supported in Flutter.


1. Understanding Error Boundaries in Flutter

Flutter's ErrorWidget replaces malfunctioning widgets on build errors, but global error handlers (FlutterError.onError and runZonedGuarded) often lack context and granularity. A custom error boundary lets you:

  • Capture errors at the widget level instead of the entire application.
  • Display fallback UIs rather than a generic red screen or crash.
  • Report contextual information upstream for debugging and observability.

Let's implement a robust, reusable error boundary widget:

import 'package:flutter/material.dart';

typedef ErrorLogger = void Function(FlutterErrorDetails details);

class ErrorBoundary extends StatefulWidget {
final Widget child;
final Widget Function(FlutterErrorDetails)? fallbackBuilder;
final ErrorLogger? onError;

const ErrorBoundary({
Key? key,
required this.child,
this.fallbackBuilder,
this.onError,
}) : super(key: key);

@override
State<ErrorBoundary> createState() => _ErrorBoundaryState();
}

class _ErrorBoundaryState extends State<ErrorBoundary> {
FlutterErrorDetails? _errorDetails;

@override
void initState() {
super.initState();
_errorDetails = null;
}

@override
Widget build(BuildContext context) {
if (_errorDetails != null) {
if (widget.fallbackBuilder != null) {
return widget.fallbackBuilder!(_errorDetails!);
}
return Center(child: Text('Oops! Something went wrong.'));
}

try {
return widget.child;
} catch (error, stack) {
final details = FlutterErrorDetails(exception: error, stack: stack);
setState(() {
_errorDetails = details;
});
widget.onError?.call(details);
return SizedBox.shrink(); // Prevents crash; fallback UI in next build.
}
}
}

Usage example:

ErrorBoundary(
child: SomeComplexWidget(),
fallbackBuilder: (details) => ErrorFallbackWidget(details: details),
onError: (details) {
// Send to your observability platform
},
)

2. Performance Implications and Optimization Tips

Implementing error boundaries introduces new code paths into your widget tree. To keep performance tight:

  • Scope boundaries surgically: Don’t wrap your entire app tree; target complex or third-party widgets, dynamic content, or historically flaky areas.
  • Avoid excessive setState: Only trigger state updates on actual errors, not on every frame.
  • Profile render times: Use flutter devtools to monitor how the error boundary affects build performance, especially in large lists or trees.
  • Cache fallback widgets: If your fallback UI is expensive to build, create it once and reuse.

Remember, the overhead of catching errors is far less costly than the damage of an unhandled crash.


3. Debugging Strategies with Error Context

Catching exceptions at the widget boundary level gives valuable debugging signal:

  • Full error details: The FlutterErrorDetails object includes the stack trace, exception, and the library.

  • Widget context: You can enrich the error log by including widget-specific data or state, for example:

    onError: (details) {
    final widgetName = context.widget.runtimeType.toString();
    sendLogToCrashlytics('Error in $widgetName', details);
    }
  • Reproducibility: Log local state values, user actions, or navigation stack at the failure point for better traceability.

Practical Tips:

  • Integrate with log aggregators (e.g., Sentry, Crashlytics) that support custom metadata and breadcrumbs.
  • Use distinct error boundary widgets for different app sections to localize errors.
  • Provide developer-centric fallback UIs in debug mode that include stack traces or error types.

4. Observability: Actionable Error Reporting

Handling the error isn’t enough-you must see it in the wild and measure impact:

Recommended Actions:

  • Log every caught error with:

    • Widget identity (name, type, state)
    • User/app session details
    • Stack trace
    • Device/environment info
  • Use structured error reporting:

    onError: (details) {
    // Example with Sentry
    Sentry.captureException(
    details.exception,
    stackTrace: details.stack,
    withScope: (scope) {
    scope.setExtra('widget', context.widget.runtimeType.toString());
    },
    );
    }
  • Analyze error volume and affected users to prioritize fixes.

  • Consider exposing a feedback option in the fallback UI for beta or QA builds:

    fallbackBuilder: (details) => Column(
    children: [
    Text('A problem occurred.'),
    ElevatedButton(
    onPressed: () => launchReportFlow(details),
    child: Text('Send Feedback'),
    ),
    ],
    )

5. Ensuring Reliability at Scale

To make your error boundary pattern robust:

  • Test with QA:

    • Simulate specific failures using test harnesses or by injecting faults.
    • Validate fallback UI across devices and OS versions for consistent UX.
  • Implement Continuous Monitoring:

    • Set up dashboards for error rates, trends, and regression analysis.
    • Push fixes quickly for high-impact failures.
  • Automate Recovery where Possible:

    • Allow users to retry failed widgets (re-initialize or reload).
    • Use progressive enhancements to render partial UI where possible, instead of full blank/error states.
  • Fail Fast, But Recover Gracefully:

    • Surface recoverable errors to users, but never let a single widget failure bring down your app.

Conclusion: Shipping User-Trustworthy Flutter Apps

By implementing custom error boundaries, Flutter teams can close real-world reliability gaps: catching widget-level errors, presenting resilient fallback UIs, capturing rich debugging signals, and driving observability at depth. Performance tuning and error context are not optional-without these, even the best error boundary is just a band-aid.

Empower your engineering and QA teams to spot, debug, and fix flaky UI before users ever notice. Start small-wrap a few high-risk widgets, integrate observability, and iterate. Over time, robust error boundaries will become a cornerstone of your app’s reputation and reliability.


Key Takeaways:

  • Custom error boundaries make your Flutter UI bulletproof against unexpected failures.
  • Scoped error catching preserves app usability and debuggability.
  • Observability and actionable reporting turn silent failures into resolved incidents.
  • Performance profiling and targeted wrapping maintain smooth UX.

Forward-looking: Stay tuned for advanced patterns-like async error boundaries for FutureBuilders and platform channel error handling, taking your engineering practice to the next level.


Happy building-may your UIs be as resilient as your ambition!

Leveraging Flutter DevTools for Real-Time Performance Bottleneck Analysis

Published: · Last updated: · 6 min read
Sandra Rosa Antony
Software Engineer, Appxiom

Performance issues in mobile apps don’t just annoy users-they drive abandonment, spark negative reviews, and make life miserable for developers on-call. Whether you’re building a new feature or tracking down a subtle lag that only appears on certain hardware, Flutter DevTools offers essential capabilities to spot and resolve real-world performance problems. In this post, we’ll go deep on how to leverage Flutter DevTools for real-time performance bottleneck analysis-empowering mobile developers, QA engineers, and engineering leads to debug faster, observe more effectively, and ship reliable apps with confidence.


Introduction: Why Proactive Performance Matters

Modern users expect smooth, responsive, and visually appealing mobile apps. Even the most feature-rich product will be judged harshly if it stutters, janks, or crashes during basic interactions. As engineering teams, we have to move beyond reactive bug-fixing to proactive observability and continuous performance management.

Core Objectives for This Guide:

  • Identify real-world sources of Flutter app lag and inefficiency using Flutter DevTools
  • Demonstrate practical debugging patterns and performance analysis flows
  • Unlock actionable strategies to boost reliability and observability

We'll tackle these points step-by-step, anchoring discussion in realistic scenarios and supplying direct code and workflow snippets to up-level your Flutter debugging game.


Understanding Flutter Performance Issues: What Can Go Wrong?

Unlike native SDKs, Flutter’s rendering is managed by a custom engine layered on Dart’s VM. This architecture is powerful but introduces unique challenges:

  • Janky UI: Frames take longer than 16ms (60FPS) to render, causing visible animation hitches.
  • Memory Leaks: Widgets or objects are inadvertently retained.
  • Slow Build/Render: Expensive rebuilds of widget trees triggered by naive state management.
  • Unoptimized Network/IO: Main isolate blocked by synchronous tasks.

These issues often show up as user-facing slowdowns-sometimes only under load, on specific hardware, or amidst tricky app state. That’s where real-time observability comes in.


Real-Time Profiling with Flutter DevTools

Flutter DevTools is more than an inspector-it’s a real-time performance profiler and analytics suite. Let’s break down its most potent features for root-cause analysis:

1. Performance Tab: Frame Rendering at a Glance

When users experience "jank," your first stop should be the Performance Tab. This visualizes frame rendering as a timeline-each vertical bar represents a frame.

How To Use:

  • Open your app in debug or profile mode (flutter run --profile).
  • Connect DevTools to the running app.
  • Interact with the slow section of your app.
  • Check for red vertical bars: these indicate missed frame deadlines.

Actionable Debugging:

  • Expand slow frames to see both UI (build/layout/paint) and raster (GPU) operations.
  • Look for spikes-excessive widget rebuilds, unnecessary repaints, or long-running logic.
  • Use the call stack ("stack frames") to determine which widgets/methods consume the most time.

Example: Diagnosing an Expensive Rebuild

Suppose list scrolling becomes laggy. After recording a session in DevTools:

ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
// Expensive widget tree.
return ComplexListTile(item: items[index]);
},
)

DevTools reveals repeated rebuilds of ComplexListTile. Solution: introduce a const constructor or extract static data outside the builder.

2. CPU Profiler: Pinpointing the Hot Paths

For complex issues-like slow async data loads or background processing-the CPU Profiler is invaluable.

How To Use:

  • Trigger application flow (e.g., load a heavy screen).
  • Start CPU profiling in DevTools.
  • Stop after issue occurs; inspect the “Time Profiler” flame chart.

Use Cases:

  • Identify synchronous CPU-bound methods (string parsing, image decoding) running on the UI isolate.
  • Reveal expensive loops or function calls that block UI updates.

Actionable Tip:

  • Offload heavy work to compute() or background isolates.
Future<void> processHeavyData() async {
final result = await compute(parseLargeJson, rawJsonString);
setState(() {
parsedData = result;
});
}

Effective Debugging Strategies: Patterns That Work

It’s not just about the tool-it’s about how you wield it. Here’s how veteran Flutter engineers approach performance debugging:

Proactive Observability

  • Instrument your code with custom Timeline Events

    Timeline.startSync('Expensive Op');
    // ... code ...
    Timeline.finishSync();

    These annotations appear in the DevTools Performance timeline, making it easy to cross-reference logic with performance spikes.

  • Leverage Widget Inspector Identify unnecessary rebuilds by tracking Widget tree changes interactively.

Hot Reload vs. Hot Restart

  • Prefer Hot Reload for day-to-day UI tweaking; however, always Hot Restart or cold restart for accurate performance traces, as lingering app state or memory leaks may not be cleaned up otherwise.

Automated Performance Regression Testing

  • Use flutter drive and CI/Docker-based device farms to collect performance metrics on every pull request.
  • Store and visualize timeline traces over time-catch regressions before release.

Reliability Through Deep Observability: Beyond DevTools

Even the best profiler is only a piece of your observability puzzle. For true reliability, combine DevTools insights with production-level monitoring:

  • Integrate Crashlytics/Sentry to catch issues that only appear in the wild.
  • Add in-app performance logging-send custom metrics from key workflows to a backend.
  • Monitor memory and resource utilization: The Memory tab in DevTools can help spot leaks, but also add guards in production.

Example: Guarding Against Memory Leaks Track object allocation over time before/after navigation:

WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint('Widget tree size: ${context.widget.toString()}');
});

Tip: If object counts continually increase with navigation, you have a retention issue.


Engineering Leadership Perspective: Empowering Teams

For engineering leaders, the impact is twofold:

  • Process Suggestions:
    • Make performance profiling part of your release checklist.
    • Hold regular “profiling guild” meetings to share findings and anti-patterns.
  • Education:
    • Codify best practices (e.g., avoid rebuilding complex widgets unnecessarily).
    • Encourage a “performance is everyone’s job” culture-QA and developers both monitor the perf dashboard.

Conclusion: Ship Faster, Smoother, More Reliable Apps

Flutter DevTools transforms performance debugging from guesswork into a science. By mastering its real-time profiling features-and integrating actionable observability into your workflow-your team can:

  • Identify and resolve performance bottlenecks early and efficiently.
  • Build a culture of proactive debugging and reliability.
  • Respond to user issues with concrete data (not just intuition).

Next steps: Schedule a “profiling hour” on your next sprint, instrument key screens, and empower your entire team to become app performance champions.

Have a specific performance challenge? Share your war stories (and wins) in the comments-we’re building this mobile community together!

Best Practices for Using Location Services in Flutter

Published: · Last updated: · 6 min read
Don Peter
Cofounder and CTO, Appxiom

Best Practices for Using Location Services in Flutter

Whether you're building a delivery app, a fitness tracker, or a travel companion, location services are a core part of many mobile experiences. But integrating location tracking in Flutter isn't just about getting coordinates - it's about doing it efficiently, responsibly, and in a way that doesn't drain the battery or frustrate users.

In this guide, we'll walk through how to implement and optimize location services in Flutter apps - from picking the right package to handling permissions, minimizing battery usage, caching data, and ensuring a smooth user experience.

Improving Flutter App Start Time: Techniques That Actually Matter

Published: · Last updated: · 8 min read
Robin Alex Panicker
Cofounder and CPO, Appxiom

You know that feeling when you tap an app icon and it doesn't open instantly - not slow enough to crash, but slow enough to make you wonder?

That pause is where users start judging your app.

In Flutter, app startup time is one of those things that quietly shapes user trust. People may not complain, but they notice. And if the app feels slow right at launch, everything else feels heavier too.

The good part? Most slow startups aren't caused by Flutter itself. They usually come from how we structure our code, load assets, and initialize things. Let's walk through the most effective ways to improve Flutter app start times - step by step, like we're fixing it together.

1. Optimize Your Widget Tree

Flutter's UI is built entirely on widgets - and the way those widgets are structured directly affects how fast your app launches. A deep or overly complex widget tree means more work before the first screen appears.

Here are a few simple ways to keep things light at startup.

Use const widgets whenever you can

When a widget is marked as const, Flutter can create it at compile time instead of rebuilding it during runtime. This reduces unnecessary work during launch, especially for static UI elements like text, icons, or layout containers.

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Text('Hello, World!');
}
}

It's a small change, but across an app, it adds up.

Build lists lazily

If your first screen contains lists or grids, avoid building everything at once. Widgets like ListView.builder and GridView.builder create items only when they're about to appear on screen, which saves both time and memory during startup.

ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index]));
},
)

This keeps the initial load fast and responsive.

Keep widget nesting under control

Deeply nested widgets make layout calculations heavier. While Flutter handles complex layouts well, unnecessary nesting can slow down rendering during launch.

Try to:

  • Flatten layouts where possible
  • Use Row, Column, and Stack thoughtfully
  • Break large widgets into smaller, reusable components

A cleaner widget tree means a faster first frame - and that's what users notice first.

2. Implement Code Splitting

Not everything in your app needs to be ready the moment it opens. Code splitting helps you take advantage of that idea by loading parts of your code only when they're actually needed. This reduces the amount of work Flutter has to do during startup and helps your first screen appear faster.

Instead of shipping one large bundle, you break your app into smaller pieces and load them on demand.

Lazy loading libraries

Dart supports deferred (lazy) loading, which lets you pull in certain libraries only when a specific feature or screen is accessed. This is especially useful for rarely used features like settings screens, advanced flows, or admin-only functionality.

void loadLibraryWhenNeeded() async {
if (someCondition) {
await import('library_to_load.dart');
// Now you can use classes and functions from the imported library
}
}

By deferring non-essential code, you keep your initial bundle lean and focused. The result is a quicker startup time and a smoother first impression - without sacrificing features deeper in the app.

3. Optimize Asset Loading

Assets like images, fonts, and icons play a big role in how your app looks - but they can also slow things down if you're not careful. Loading heavy assets too early is a common reason apps feel sluggish right after launch.

A little discipline here goes a long way.

Declare assets properly

Make sure all your assets are clearly defined in your pubspec.yaml file. This allows Flutter to process and bundle them efficiently during build time, instead of figuring things out at runtime.

flutter:
assets:
- images/
- fonts/

When assets are registered correctly, Flutter knows exactly what to load and when-no surprises during startup.

Optimize and compress images

Large images are often silent performance killers. Use modern, compressed formats like WebP wherever possible, and avoid shipping images that are larger than what the UI actually needs.

If an image is only ever shown as a thumbnail, don't bundle a full-resolution version "just in case." Smaller assets mean less decoding work, faster rendering, and a quicker path to your first screen.

Think of asset optimization as packing light for a trip - the less you carry at the start, the faster you move.

4. Use Ahead-of-Time (AOT) Compilation

Flutter gives you two ways to run your code: Just-in-Time (JIT) and Ahead-of-Time (AOT). During development, JIT is great - it enables hot reload and faster iteration. But when it comes to app startup speed, AOT is where the real gains are.

With AOT compilation, your Dart code is compiled into native machine code before the app ever reaches a user's device. That means less work during launch and a noticeably faster startup.

How to enable AOT

AOT compilation is automatically applied when you build your app in release mode. Just use the release flag when generating your build:

flutter build apk --release

This is the version users download from the app store - and it's optimized for speed and performance, not debugging convenience.

In short: JIT helps you build faster, AOT helps your app launch faster. And when startup time matters, AOT is non-negotiable.

5. Profile and Optimize Performance

Guessing where your app is slow rarely works. The fastest way to improve startup time is to measure first, then optimize with intention. That's where profiling comes in.

Flutter ships with Flutter DevTools, a powerful set of performance tools that show you exactly what's happening under the hood - frame by frame.

Using Flutter DevTools

DevTools lets you inspect widget rebuilds, rendering times, CPU usage, and frame drops. Instead of guessing, you can see which parts of your app are doing extra work during launch.

Once DevTools is installed and running, connect it to your app and explore the performance and timeline views. These screens reveal how long widgets take to render and where delays creep in.

flutter pub global activate devtools
flutter pub global run devtools

Fix what the data shows

Profiling often surfaces familiar issues:

  • Widgets rebuilding more often than necessary
  • Heavy work happening during the first frame
  • Frames taking too long to render, causing jank

The key is to focus only on what the profiler highlights. Small changes - like caching results, reducing rebuilds, or deferring non-essential work - can dramatically improve startup performance when guided by real data.

6. Minimize Initial Plugin Loading

Not every plugin needs to wake up the moment your app launches. Some plugins perform setup work as soon as the app starts, and that extra initialization time can quietly slow things down.

A simple rule of thumb: only load what you actually need at startup.

If a plugin supports delayed initialization, move that setup to the moment it's required - such as when a specific screen is opened or a feature is triggered. This keeps your app lightweight during launch and pushes non-essential work a little later, when users are already interacting with the app.

For example, instead of initializing a plugin on app start, you can wait until a certain condition is met and then initialize it on demand. This small change can shave noticeable time off your startup flow, especially in apps that rely on multiple third-party plugins.

Future<void> initializePluginsWhenNeeded() async {
if (someCondition) {
await MyPlugin.init();
}
}

Think of startup as a first impression. The less work your app does upfront, the faster it feels - and the happier your users will be.

7. Optimize Third-Party Dependencies

Every dependency you add comes with a cost. Keep your startup lean by including only the libraries your app truly needs, and remove anything that's unused or redundant. It also helps to keep dependencies up to date - many libraries improve performance over time, and those small gains can add up during app launch.

Conclusion

A fast app launch sets the tone for everything that follows. When your Flutter app opens quickly, users feel that polish and responsiveness right away - and they're far more likely to stick around.

By keeping your widget tree lean, loading code and plugins only when needed, being intentional with assets, using AOT builds for release, and regularly profiling performance, you're removing friction from the very first interaction a user has with your app. None of these changes are drastic on their own, but together, they make a noticeable difference.

Also remember - performance isn't a one-time fix. As features grow and dependencies change, it's worth revisiting startup behavior from time to time. A few small adjustments can save your users from long splash screens and give your app that "snappy" feel everyone loves.

Keep experimenting, keep measuring, and keep shipping smoother experiences.

A Practical Guide to Optimizing Your Flutter App with Dart Analyzer

Published: · Last updated: · 5 min read
Sandra Rosa Antony
Software Engineer, Appxiom

If you've worked on a Flutter app for more than a few weeks, you've probably had this moment: the app works, the UI looks fine… but the code? It's slowly getting messy. A few unused variables here, a couple of print statements there, inconsistent styles everywhere. Nothing is broken yet, but you can feel future bugs lining up.

This is exactly where the Dart Analyzer quietly saves you.

Flutter ships with a static code analysis tool that watches your code while you write it and points out problems before they turn into crashes, performance issues, or painful refactors. The best part? Most teams barely scratch the surface of what it can do.

Let's walk through how the Dart Analyzer works, how you can customize it, and how a few small lint tweaks can make your Flutter app noticeably cleaner and easier to maintain.

Best Practices to Avoid Memory Leaks in Flutter Apps

Published: · Last updated: · 5 min read
Don Peter
Cofounder and CTO, Appxiom

You know that feeling when your Flutter app works perfectly in testing… but starts lagging, stuttering, or crashing after users spend some time in it? That's often not a "Flutter problem." It's a memory problem.

Memory leaks are sneaky. They don't always break your app immediately. Instead, they quietly pile up - using more RAM, slowing things down, and eventually pushing your app to a crash. The good news? Most memory leaks in Flutter are avoidable once you know where to look.

Let's walk through some practical, real-world ways to prevent memory leaks in Flutter - no fluff, just things you can actually apply.

Integrating url_launcher in Flutter Apps

Published: · Last updated: · 4 min read
Don Peter
Cofounder and CTO, Appxiom

The mobile app development world has moved from fast to ‘impatiently fast’. One essential aspect of faster user interaction is the ability to navigate to external websites or open other apps directly from within your Flutter application.

This is where the url_launcher plugin for Flutter comes into play. This plugin allows you to open URLs in the default web browser of the device. It also allows for ​​opening URLs that launch other apps installed on the device such as emails or social media apps. 

Installing URL Launcher in Flutter

Installation can be done in a whiff by following the code given below: 

Terminal Command

flutter pub add url_launcher

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
url_launcher: x.y.z.

Supported URL Schemes

url_launcher supports various URL schemes. They are essentially prefixes or protocols that help define how a URL should be handled by Android, iOS or any operating system or apps in general. Common URL Schemes supported by url_launcher include HTTP, HTTPS, mailto, SMS, tel, App Schemes and Custom Schemes. 

Integrating url_launcher

When using the url_launcher package, you can open URLs with these schemes using the launch function. This package will delegate the URL handling to the underlying platform, ensuring compatibility with both Android and iOS. 

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class MyApp extends StatelessWidget {

@override Widget build(BuildContext context) {

&nbsp;&nbsp;&nbsp;&nbsp;return MaterialApp(

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;home: Scaffold(

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;appBar: AppBar(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: Text('URL Launcher Example'),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;),

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;body: Center(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child: ElevatedButton(

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onPressed: () {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_openURL("https://appxiom.com/");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;child: Text('Open URL'),

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;),

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;),

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;),

&nbsp;&nbsp;&nbsp;&nbsp;);

&nbsp;&nbsp;}

&nbsp;&nbsp;// Function to open a URL using url_launcher

&nbsp;&nbsp;void _openURL(String url) async {

&nbsp;&nbsp;&nbsp;&nbsp;if (await canLaunchUrl(url)) { //Checking if there is any app installed in the device to handle the url.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;await launch(url);

&nbsp;&nbsp;&nbsp;&nbsp;} else {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Handle error

&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;}

}

Configuring canLaunchUrl in iOS

Make sure to add the URL schemes passed to canLaunchUrl as LSApplicationQueriesSchemes entries in your info.plist file. Otherwise, it will return false.

&lt;key&gt;LSApplicationQueriesSchemes&lt;/key&gt;

&lt;array&gt;

&nbsp;&nbsp;&lt;string&gt;sms&lt;/string&gt;

&nbsp;&nbsp;&lt;string&gt;tel&lt;/string&gt;

&lt;/array&gt;

Configuring canLaunchUrl in Android

Add the URL schemes passed to canLaunchUrl as <queries> entries in your AndroidManifest.xml, otherwise, it will return false in most cases starting on Android 11 (API 30) or higher. 

&lt;!-- Provide required visibility configuration for API level 30 and above --&gt;

&lt;queries&gt;

&nbsp;&nbsp;&lt;!-- If your app checks for SMS support --&gt;

&nbsp;&nbsp;&lt;intent&gt;

&nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.intent.action.VIEW" /&gt;

&nbsp;&nbsp;&nbsp;&nbsp;&lt;data android:scheme="sms" /&gt;

&nbsp;&nbsp;&lt;/intent&gt;

&nbsp;&nbsp;&lt;!-- If your app checks for call support --&gt;

&nbsp;&nbsp;&lt;intent&gt;

&nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.intent.action.VIEW" /&gt;

&nbsp;&nbsp;&nbsp;&nbsp;&lt;data android:scheme="tel" /&gt;

&nbsp;&nbsp;&lt;/intent&gt;

&nbsp;&nbsp;&lt;!-- If your application checks for inAppBrowserView launch mode support --&gt;

&nbsp;&nbsp;&lt;intent&gt;

&nbsp;&nbsp;&nbsp;&nbsp;&lt;action android:name="android.support.customtabs.action.CustomTabsService" /&gt;

&nbsp;&nbsp;&lt;/intent&gt;

&lt;/queries&gt;

That’s it for now. For more information on  url_launcher with Flutter, check https://pub.dev/packages/url_launcher/

Guide on Optimizing Flutter App Using Dart Analyzer

Published: · Last updated: · 4 min read
Don Peter
Cofounder and CTO, Appxiom

Flutter projects come equipped with a dart analyzer for static code analysis. When a project is created with Flutter version 2.3.0 and above, a default configuration file named analysis_options.yaml will be generated at the root of the project. Analyzer tool runs checks based on the checks set in this configuration file.

Lints are a set of rules to check the code for potential errors or formatting issues. The configuration file analysis_options.yaml has a set of recommended lints for Flutter applications, packages, and plugins. This is achieved through the automatic inclusion of package:flutter_lints/flutter.yaml.

include: package:flutter_lints/flutter.yaml

linter:
rules:

Dart-enabled Integrated Development Environments (IDEs) like visual studio code typically display the issues detected by the analyzer in their user interface. Alternatively, you can manually run the analyzer by executing flutter analyze from the terminal to identify and address code issues.

In this blog post, we'll delve into how to customize the lint rules, empowering developers to tailor it to their specific needs.

Customizing Lint Rules in Flutter

The real power of the Dart analyzer configuration lies in the ability to customize lint rules according to your project's requirements. The linter section allows developers to fine-tune lint rules, either by disabling those inherited from flutter.yaml or by enabling additional rules.

Warning: Linter rules may throw false positives.

Configuring Lint rules at the Project level

The analysis_options.yaml configuration file allows developers to customize lint rules at the project level.

linter:
rules:
avoid_print: false
prefer_single_quotes: true

In this example, the avoid_print rule is disabled by setting it to false, and the prefer_single_quotes rule is enabled by setting it to true. This level of granular control allows developers to enforce or relax specific rules based on their project's coding standards.

Configuring Lint rules at File/code level

In addition to configuring lint rules in the global scope as shown above, developers can suppress lints for specific lines of code or files using comments.

The syntax // ignore: name_of_lint or // ignore_for_file: name_of_lint can be used to silence lint warnings on a case-by-case basis.

// ignore_for_file: name_of_lint
class&nbsp;Class&nbsp;{
// ignote: name_of_lint
var&nbsp;_count = 0;

var&nbsp;_count2 = 0;
}

Sample Case Studies

Now, let us dive into couple of lint rules to get a better idea of what exactly these rules can do.

Omitting explicit Local variable types

In situations where functions tend to be concise, local variables often have limited scope. Omitting the variable type helps shift the reader's focus toward the variable's name and its initialized value, which are often more crucial aspects.

With explicit types

List&lt;List&lt;FoodItem&gt;&gt; findMatchingMeals(Set&lt;FoodItem&gt; kitchen) {
List&lt;List&lt;FoodItem&gt;&gt; meals = &lt;List&lt;FoodItem&gt;&gt;[];
for (final List&lt;FoodItem&gt; mealRecipe in recipeBook) {
if (kitchen.containsAll(mealRecipe)) {
meals.add(mealRecipe);
}
}
return meals;
}

Without explicit types

List&lt;List&lt;FoodItem&gt;&gt; findMatchingMeals(Set&lt;FoodItem&gt; kitchen) {
var meals = &lt;List&lt;FoodItem&gt;&gt;[];
for (final mealRecipe in recipeBook) {
if (kitchen.containsAll(mealRecipe)) {
meals.add(mealRecipe);
}
}
return meals;
}

To warn if explicit type is used in the local variables, use the lint rule omit_local_variable_types,

linter:
rules:
-&nbsp;omit_local_variable_types

Disable avoid_print in lint rules

It is always advisable to avoid incorporating print statements into production code. Instead, you can opt for debugPrint or enclose print statements within a condition checking for kDebugMode.

void&nbsp;processItem(int&nbsp;itemId)&nbsp;{
debugPrint('debug: $x');
...
}

void processItem(int itemId) {
if (kDebugMode) {
print('debug: $x');
}
...
}

By default, print statements are flagged by the analyzer.

With lint rules you can override this, set avoid_print to false as shown below,

linter:
rules:
-&nbsp;omit_local_variable_types
avoid_print: false

Conclusion

Customizing the Dart analyzer is a pivotal step in elevating your Flutter development. Begin by activating recommended lints for Flutter, encouraging good coding practices.

The real power lies in the ability to finely tune lint rules at both project and file levels, granting granular control over code standards. Use comments judiciously to suppress lints where needed.

Lastly, The Dart language provides an extensive list of available lint rules, each documented on the official Dart website https://dart.dev/tools/linter-rules#rules.

Logging in Flutter

Published: · Last updated: · 3 min read
Don Peter
Cofounder and CTO, Appxiom

Logging plays a vital role in debugging, monitoring, and analyzing the behavior of your Flutter application. Choosing the right logging method and plugin can significantly improve your development workflow and overall code quality.

This blog post explores different logging options available in Flutter, their advantages and disadvantages for each.

Native Logging Methods

These are the most basic logging methods provided by Flutter and Dart. They offer simple syntax and are easily accessible. However, they lack log levels, filtering options, and other features essential for comprehensive logging.

print('This is a simple log message'); 

debugPrint('This message is only visible in debug mode');

To prevent the potential loss of log lines due to Android's log line discarding mechanism when outputting an excessive amount of logs at once, it is advisable to utilize debugPrint() from Flutter's foundation library. This function acts as a wrapper around the standard print method, implementing throttling to ensure that the log output remains at a level that avoids being discarded by Android's kernel.

dart:developer:

This package provides more advanced logging features than print(). It allows logging messages with more granularity.

import 'dart:convert';
import 'dart:developer';

class User {
// Define your custom object properties and methods here.
}

void main() {
// Create an instance of your custom object.
var user = User();

// Log a message with additional application data using the error parameter.
log(
'This is getting logged',
name: 'some.id',
error: jsonEncode(user),
);
}

The log function is used to log a message. The name parameter is used to specify the logging category, and the error parameter is employed to pass additional application data. In this case, the jsonEncode function is used to encode the custom object as a JSON string before passing it to the error parameter.

This approach allows you to view the JSON-encoded data as a structured object when examining the log entries in tools like DevTools.

Advantages

  • Easy to implement and understand.

  • Built-in with Flutter and Dart.

  • Suitable for simple logging needs.

Disadvantages

  • Lack of filtering options and log levels.

  • Not ideal for complex applications with extensive logging needs.

Third party Logging Package

Logger:

Logger is a popular package that offers a powerful and flexible logging API. It provides various log levels, custom filters, and different output destinations.

var logger = Logger(
filter: null,
printer: PrettyPrinter(),
output: null,
);

logger.i('Starting the application');
logger.w('A warning message', error: error);

Link to logger plugin: https://pub.dev/packages/logger

Advantages:

  • More features and flexibility than native methods.

  • Can be extended to have support with external services and platforms.

  • Customizable output and filtering options.

Disadvantages:

  • May require additional setup and dependencies.

  • Can be more complex to use.

Choosing the Right Logging Method

The best logging method for your project depends on your specific needs and requirements. Here are some factors to consider:

  • Project Size and Complexity: Simple projects with minimal logging needs might benefit from native methods like print() or dart:developer. For larger and more complex applications, consider using a dedicated logging package like Logger.

  • Performance Considerations: For performance-critical applications, lightweight packages should be used.

By understanding the advantages and disadvantages of each option and considering your specific requirements, you can make an informed decision and ensure your application is well-equipped for success.

A Beginners Guide to Integrating SQFlite in Flutter Projects

Published: · Last updated: · 4 min read
Don Peter
Cofounder and CTO, Appxiom

In the world of mobile app development, the need for robust and efficient data management solutions is paramount. When building complex Flutter applications that require local data storage and management, this can prove to be a game-changer.

What is Sqflite?

Sqflite is a Flutter plugin that provides a simple and efficient way to implement SQLite databases in your Flutter applications. With Sqflite, you can perform various database operations, such as creating, querying, updating, and deleting data, making it an essential tool for managing local data storage in your Flutter projects.

Its simplicity, coupled with its powerful capabilities, makes it a popular choice for developers looking to incorporate local database functionality into their applications.

How to Integrate Sqflite in Flutter Projects

Integrating Sqflite into your Flutter projects is a straightforward process. Follow these simple steps to get started:

Add the Dependency

Open your project's pubspec.yaml file and add the Sqflite dependency:

dependencies:
sqflite: ^2.3.0

Install the Dependency

After adding the dependency, run the following command in your terminal:

flutter pub get

Import Sqflite

Import the Sqflite package into your Dart code:

import 'package:sqflite/sqflite.dart';

Use Sqflite API

Utilize the Sqflite API to create and manage your SQLite database operations. You can create tables, execute queries, and perform various data manipulation tasks.

Creating a Database and Table

Here we create a SQLite database and a table within that database using Sqflite in Flutter. It utilizes the openDatabase method to create a new database or open an existing one.

The onCreate callback is used to execute a SQL command that creates a table named "Users" with three columns: "id" as the primary key, "username" as a TEXT type, and "age" as an INTEGER type. Additionally, it retrieves the path for the database using getDatabasesPath() and joins it with the database name "example.db".

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

Future&lt;void&gt; createDatabase() async {
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'my_database.db');

// Delete any existing database:
await deleteDatabase(path);

// Create the database
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute('
CREATE TABLE Users (
id INTEGER PRIMARY KEY,
username TEXT,
age INTEGER
)
');
});
}

Inserting a Row

To insert a row into the "Users" table in the SQLite database, it utilizes the insert method, which takes the table name, a map representing the data to be inserted, and an optional conflictAlgorithm parameter to handle conflicts that may arise during the insertion process.

In this case, if there is a conflict, the existing row is replaced with the new data.

Future&lt;void&gt; insertData(Database database) async {
await database.insert(
'Users',
{'username': 'Alice', 'age': 30},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}

Selecting Data

To perform a simple query to retrieve data from the "Users" table in the SQLite database, it utilizes the query method, which takes the table name as a parameter and returns a list of maps representing the queried rows. The retrieved data can then be used for further processing or display purposes.

Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; queryData(Database database) async {
return await database.query('Users');
}

Custom SQL query

To execute a custom SQL query that selects all rows from the "Users" table where the value of the "age" column is greater than 25. The rawQuery method allows you to execute custom SQL queries directly.

Make sure to handle the results appropriately based on the specific requirements of your application.

Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; customQuery(Database database) async {
return await database.rawQuery('SELECT * FROM "Users" WHERE age &gt; 25');
}

Deleting a Row

To delete a specific row from the "Users" table in the SQLite database based on a provided condition, it uses the delete method, which takes the table name, a where clause specifying the condition for deletion, and optional whereArgs to provide values for the placeholders in the where clause. The method returns the number of rows deleted as an integer.

Future&lt;int&gt; deleteData(Database database, int id) async {
return await database.delete('Users', where: 'id = ?', whereArgs: [id]);
}

Conclusion

In conclusion, integrating Sqflite in your Flutter projects can significantly enhance the performance and user experience of your applications.

Its simplicity, efficiency, and powerful data management capabilities make it an indispensable tool for managing local data storage and operations. By following the steps outlined in this guide and leveraging Sqflite's robust features, you can create powerful Flutter applications that deliver a seamless and efficient user experience.

Implementing and Using Data Structures in Dart

Published: · Last updated: · 5 min read
Robin Alex Panicker
Cofounder and CPO, Appxiom

Dart is a versatile and powerful programming language that has gained popularity for building web, mobile, and desktop applications, thanks to the Flutter framework. To harness its full potential, it's essential to understand and implement various data structures.

In this blog, we'll explore some common data structures and demonstrate how to implement and use them in Dart.

Data Structures in Dart

Data structures are fundamental for organizing and managing data efficiently. Dart provides built-in support for a variety of data structures and allows you to create custom ones. Some common data structures in Dart include:

  • Lists

  • Sets

  • Maps

  • Queues

  • Stacks

  • Trees

  • Graphs

We'll delve into each of these data structures, provide code samples, and discuss their use cases.

Lists

Lists in Dart are ordered collections of objects. They are similar to arrays in other languages and are incredibly versatile. Here's how to create and manipulate lists:

// Creating a List
List&lt;int&gt; numbers = [1, 2, 3, 4, 5];

// Accessing elements
int firstNumber = numbers[0]; // Access the first element (1)

// Modifying elements
numbers[2] = 10; // Update the third element to 10

// Adding elements
numbers.add(6); // Add 6 to the end of the list

// Removing elements
numbers.remove(2); // Remove the element with value 2

// Iterating through a list
for (var number in numbers) {
print(number);
}

Sets

Sets are unordered collections of unique elements. Dart's Set ensures that each element is unique, making them suitable for maintaining unique values:

// Creating a Set
Set&lt;String&gt; uniqueColors = {'red', 'blue', 'green'};

// Adding elements
uniqueColors.add('yellow');

// Removing elements
uniqueColors.remove('red');

// Iterating through a set
for (var color in uniqueColors) {
print(color);
}

Maps

Maps, also known as dictionaries, are collections of key-value pairs. In Dart, maps are implemented using the Map class:

// Creating a Map
Map&lt;String, int&gt; ages = {'Alice': 25, 'Bob': 30, 'Charlie': 22};

// Accessing values
int aliceAge = ages['Alice']; // Access Alice's age (25)

// Modifying values
ages['Bob'] = 31; // Update Bob's age to 31

// Adding new key-value pairs
ages['David'] = 28; // Add a new entry

// Removing key-value pairs
ages.remove('Charlie'); // Remove Charlie's entry

// Iterating through a map
ages.forEach((name, age) {
print('$name is $age years old');
});

Queues

A queue is a data structure that follows the First-In-First-Out (FIFO) principle. In Dart, you can create a simple Queue data structure using a custom class:

class Queue&lt;T&gt; {
List&lt;T&gt; _items = [];

void enqueue(T item) {
_items.add(item);
}

T dequeue() {
if (_items.isNotEmpty) {
return _items.removeAt(0);
}
return null;
}

int get length =&gt; _items.length;
}

You can then use this custom Queue class as follows:

var myQueue = Queue&lt;int&gt;();
myQueue.enqueue(1);
myQueue.enqueue(2);
myQueue.enqueue(3);

print(myQueue.dequeue()); // 1

Queues are useful for tasks that require managing elements in the order they were added, such as task scheduling or breadth-first search in graphs.

Stacks

A stack is another fundamental data structure that follows the Last-In-First-Out (LIFO) principle. While Dart doesn't provide a built-in Stack class, you can easily implement one using a custom class:

class Stack&lt;T&gt; {
List&lt;T&gt; _items = [];

void push(T item) {
_items.add(item);
}

T pop() {
if (_items.isNotEmpty) {
return _items.removeLast();
}
return null;
}

int get length =&gt; _items.length;
}

You can use this custom Stack class as follows:

var myStack = Stack&lt;int&gt;();
myStack.push(1);
myStack.push(2);
myStack.push(3);

print(myStack.pop()); // 3

Stacks are often used for tasks like managing function calls, parsing expressions, and implementing undo/redo functionality in applications.

Trees

Trees are hierarchical data structures with nodes connected by edges. They are commonly used for organizing data, searching, and representing hierarchical relationships. In Dart, you can create tree-like structures by defining custom classes that represent nodes. Here's a basic example of a binary tree:

class TreeNode&lt;T&gt; {
T value;
TreeNode&lt;T&gt; left;
TreeNode&lt;T&gt; right;

TreeNode(this.value);
}

You can then build a tree structure by connecting these nodes. Tree data structures come in various forms, including binary trees, AVL trees, and B-trees, each suited for specific tasks.

Graphs

Graphs are complex data structures that consist of nodes and edges. They are used to represent relationships between objects and solve problems such as network routing, social network analysis, and more. In Dart, you can create a basic graph using a custom class to represent nodes and edges:

class Graph&lt;T&gt; {
Map&lt;T, List&lt;T&gt;&gt; _adjacencyList = {};

void addNode(T node) {
if (!_adjacencyList.containsKey(node)) {
_adjacencyList[node] = [];
}
}

void addEdge(T node1, T node2) {
_adjacencyList[node1].add(node2);
_adjacencyList[node2].add(node1); // For an undirected graph
}

List&lt;T&gt; getNeighbors(T node) {
return _adjacencyList[node];
}
}

void main() {
var graph = Graph&lt;String&gt;();

graph.addNode('A');
graph.addNode('B');
graph.addNode('C');
graph.addNode('D');

graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');

print(graph.getNeighbors('A')); // [B, C]
print(graph.getNeighbors('B')); // [A, D]
}

This is a basic implementation of an undirected graph in Dart. You can expand upon this to create more complex graphs and perform various operations.

Conclusion

Understanding and implementing data structures in Dart is essential for efficient and organized data manipulation in your programs.

Lists, Sets, and Maps are the built-in data structures that come in handy for most scenarios, but you can create custom data structures like Queues and Stacks when necessary. Trees and Graphs are more complex data structures that can be implemented through custom classes to solve specific problems.

With this knowledge, you'll be better equipped to tackle a wide range of programming challenges in Dart.

Dio Plugin Integration with Dart / Flutter: For Beginners

Published: · Last updated: · 3 min read
Don Peter
Cofounder and CTO, Appxiom

Dio is a popular HTTP client library for Dart and Flutter. It provides a comprehensive and high-performance API for making HTTP requests, with support for multiple core features.

Why use Dio in Flutter?

Dio offers a number of advantages over the built-in http package in Flutter, including:

  • More features: Dio provides a wider range of features than the http package, such as global configuration, interceptors, and request cancellation.

  • Better performance: Dio is generally considered to be more performant than the http package, especially for complex requests.

  • Easier to use: Dio provides an intuitive and easy-to-use API, making it a good choice for both beginners and experienced developers.

How to integrate Dio with a Flutter project

To integrate Dio with a Flutter project, you can follow these steps:

  • Add the Dio dependency to your pubspec.yaml file:
dependencies:
dio: ^5.3.3
  • Run flutter pub get to install the Dio package.

  • Create a new Dio instance:

import 'package:dio/dio.dart';

class MyApiClient {
final dio = Dio();
}
  • Make HTTP requests using the Dio instance:
Future&lt;Response&gt; get(String url) async {
return await dio.get(url);
}

Future&lt;Response&gt; post(String url, dynamic data) async {
return await dio.post(url, data: data);
}
  • Handle errors:
try {
Response response = await dio.get(url);

// Handle the response
} catch (e) {
// Handle the error
}

Features of the Dio plugin

Dio provides a number of features that make it a powerful and versatile HTTP client for Flutter, including:

  • Global configuration:Dio allows you to set global configurations that apply to all requests made by the client. This includes options like setting default headers, base URLs, and more.

  • Interceptors: Dio supports interceptors, which allow you to intercept and modify requests and responses. This can be used to implement features such as authentication, logging, and caching.

  • Request cancellation: Dio allows you to cancel requests in progress. This can be useful if you need to stop a request that is no longer needed.

  • File downloading: Dio provides a built-in file downloader that can be used to download files from the server.

  • Timeout: Dio allows you to set a timeout for requests. This can be useful to prevent requests from hanging indefinitely.

Disadvantages of using Dio over http in Flutter

Dio has a few potential disadvantages over the built-in http package in Flutter, including:

  • Larger package size: The Dio package is larger than the http package, which can increase the size of your Flutter app.

  • Steeper learning curve: Dio provides more features than the http package, which can make it more difficult to learn.

  • Community support: The http package is more widely used than Dio, so there is a larger community of developers who can provide support.

Overall, Dio is a powerful and versatile HTTP client for Flutter that offers a number of advantages over the built-in http package.

Best Practices to Avoid Memory Leaks in Flutter

Published: · Last updated: · 3 min read
Appxiom Team
Mobile App Performance Experts

Memory leaks can be a common issue in mobile app development, including Flutter applications. When memory leaks occur, they can lead to reduced performance, increased memory consumption, and ultimately, app crashes. Flutter developers must be proactive in identifying and preventing memory leaks to ensure their apps run smoothly.

In this blog post, we will explore some best practices to help you avoid memory leaks in your Flutter applications, complete with code examples.

1. Use Weak References

One of the most common causes of memory leaks in Flutter is holding strong references to objects that are no longer needed. To prevent this, use weak references when appropriate. Weak references allow objects to be garbage collected when they are no longer in use.

Here's an example of how to use weak references in Flutter:

import 'dart:ui';

class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() =&gt; _MyWidgetState();
}

class _MyWidgetState extends State&lt;MyWidget&gt; {
// Use a weak reference to avoid memory leaks
final _myObject = WeakReference&lt;MyObject&gt;();

@override
void initState() {
super.initState();
// Create an instance of MyObject
_myObject.value = MyObject();
}

@override
Widget build(BuildContext context) {
// Use _myObject.value in your widget
return Text(_myObject.value?.someProperty ?? 'No data');
}
}

2. Dispose of Resources

In Flutter, widgets that use resources such as animations, controllers, or streams should be disposed of when they are no longer needed. Failure to do so can result in memory leaks.

Here's an example of how to dispose of resources using the dispose method:

class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() =&gt; _MyWidgetState();
}

class _MyWidgetState extends State&lt;MyWidget&gt; {
AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose(); // Dispose of the animation controller
super.dispose();
}

@override
Widget build(BuildContext context) {
// Use the _controller for animations
return Container();
}
}

3. Use WidgetsBindingObserver

Flutter provides the WidgetsBindingObserver mixin, which allows you to listen for app lifecycle events and manage resources accordingly. You can use it to release resources when the app goes into the background or is no longer active.

Here's an example of how to use WidgetsBindingObserver:

class MyWidget extends StatefulWidget with WidgetsBindingObserver {
@override
_MyWidgetState createState() =&gt; _MyWidgetState();

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
// Release resources when the app goes into the background
_releaseResources();
} else if (state == AppLifecycleState.resumed) {
// Initialize resources when the app is resumed
_initializeResources();
}
}

void _initializeResources() {
// Initialize your resources here
}

void _releaseResources() {
// Release your resources here
}
}

4. Use Flutter DevTools

Flutter DevTools is a powerful set of tools that can help you identify and diagnose memory leaks in your Flutter app. It provides insights into memory usage, object allocation, and more. To use Flutter DevTools, follow these steps:

  • Ensure you have Flutter DevTools installed:
flutter pub global activate devtools
  • Run your app with DevTools:
flutter run
  • Open DevTools in a web browser:
flutter pub global run devtools
  • Use the Memory and Performance tabs to analyze memory usage and detect leaks.

5. Use APM Tools

Even if a thorough testing is done, chances of memory leaks happening in production cannot be ruled out. Use APM tools like Appxiom that monitors memory leaks and reports in real time, both in development phase and production phase.

Conclusion

Memory leaks can be a challenging issue to deal with in Flutter apps, but by following these best practices and using tools like Flutter DevTools and Appxiom, you can significantly reduce the risk of memory leaks and keep your app running smoothly. Remember to use weak references, dispose of resources properly, and manage resources based on app lifecycle events to ensure your Flutter app remains efficient and stable.

Happy Coding!

Integrating and Using Charts in Flutter

Published: · Last updated: · 4 min read
Appxiom Team
Mobile App Performance Experts

Data visualization is a crucial aspect of mobile app development. Flutter, a popular open-source framework for building natively compiled applications for mobile, web, and desktop from a single codebase, offers various libraries and tools to integrate and use charts effectively.

In this article, we will explore how to integrate and use charts in Flutter applications.

Let's dive in!

1. Setting Up a Flutter Project

Before we begin, make sure you have Flutter installed on your system. If not, you can follow the official Flutter installation guide: https://flutter.dev/docs/get-started/install

Once Flutter is set up, create a new Flutter project using the following command:

flutter create flutter_chart_example

Navigate to the project directory:

cd flutter_chart_example

Now, you're ready to integrate charts into your Flutter app.

2. Choosing a Charting Library

Flutter offers various charting libraries to choose from, including fl_chart, charts_flutter, and syncfusion_flutter_charts.

In this article, we'll use fl_chart, a versatile and customizable charting library.

3. Installing the Charting Library

Open the pubspec.yaml file in your Flutter project and add the fl_chart dependency:

dependencies:
flutter:
sdk: flutter
fl_chart: ^0.63.0

Run flutter pub get to install the dependency:

flutter pub get

4. Creating a Basic Chart

Let's create a basic line chart to display some sample data. Open the main.dart file and replace its content with the following code:

import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Chart Example'),
),
body: Center(
child: LineChart(
LineChartData(
gridData: FlGridData(show: false),
titlesData: FlTitlesData(show: false),
borderData: FlBorderData(
show: true,
border: Border.all(
color: const Color(0xff37434d),
width: 1,
),
),
minX: 0,
maxX: 7,
minY: 0,
maxY: 6,
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(0, 3),
FlSpot(1, 1),
FlSpot(2, 4),
FlSpot(3, 2),
FlSpot(4, 5),
FlSpot(5, 3),
FlSpot(6, 4),
],
isCurved: true,
colors: [Colors.blue],
dotData: FlDotData(show: false),
belowBarData: BarAreaData(show: false),
),
],
),
),
),
),
);
}
}

This code creates a basic line chart with sample data. It sets up the chart appearance and defines the data points.

5. Customizing the Chart

You can customize the chart further by tweaking its appearance, labels, and more. Explore the fl_chart documentation (https://pub.dev/packages/fl_chart) to learn about various customization options.

6. Adding Interactivity

To make your chart interactive, you can implement gestures like tap or swipe. The fl_chart library provides gesture support for charts. Refer to the documentation for details on adding interactivity.

7. Real-World Example: Stock Price Chart

As a more advanced example, let's create a stock price chart with historical data fetched from an API. We'll use the http package to make API requests.

// Import necessary packages at the top of main.dart
import 'package:http/http.dart' as http;
import 'dart:convert';

// Create a function to fetch stock price data
Future&lt;List&lt;FlSpot&gt;&gt; fetchStockPriceData() async {
final response = await http.get(Uri.parse('YOUR_API_ENDPOINT_HERE'));

if (response.statusCode == 200) {
final List&lt;dynamic&gt; data = json.decode(response.body);
final List&lt;FlSpot&gt; spots = [];

for (var i = 0; i &lt; data.length; i++) {
spots.add(FlSpot(i.toDouble(), data[i]['price'].toDouble()));
}

return spots;
} else {
throw Exception('Failed to load stock price data');
}
}

// Inside the LineChart widget, replace the spots with fetched data
lineBarsData: [
LineChartBarData(
spots: await fetchStockPriceData(), // Fetch and populate data
isCurved: true,
colors: [Colors.blue],
dotData: FlDotData(show: false),
belowBarData: BarAreaData(show: false),
),
],

Replace 'YOUR_API_ENDPOINT_HERE' with the actual API endpoint that provides historical stock price data in JSON format.

Conclusion

In this article, we explored how to integrate and use charts in Flutter applications. We started by setting up a Flutter project, choosing a charting library, and installing the fl_chart package. We created a basic line chart, customized it, and discussed adding interactivity. Finally, we implemented a real-world example of a stock price chart with data fetched from an API.

Charts are essential for visualizing data and providing insights in your Flutter applications. With the fl_chart library and the knowledge gained from this tutorial, you can create visually appealing and interactive charts to enhance your app's user experience.

Happy charting!

Integrating and Using Firestore in Flutter Apps

Published: · Last updated: · 5 min read
Appxiom Team
Mobile App Performance Experts

Firestore is a powerful NoSQL database offered by Firebase, a platform provided by Google. It's a perfect fit for building real-time, cloud-hosted applications.

In this article, we'll explore how to integrate Firestore into a Flutter application and build a complete CRUD (Create, Read, Update, Delete) address book application. By the end of this tutorial, you'll have a fully functional address book app that allows you to manage your contacts.

Prerequisites

Before we begin, ensure you have the following prerequisites:

  • Flutter Environment: Make sure you have Flutter installed and set up on your development machine. You can get started with Flutter by following the official installation guide.

  • Firebase Account: Create a Firebase account (if you don't have one) and set up a new project on the Firebase Console.

  • FlutterFire Dependencies: We'll use the cloud_firestore package to interact with Firestore. Add the following dependency to your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
cloud_firestore: ^4.9.1

Run flutter pub get to fetch the package.

Setting up Firestore

  • Firebase Project Configuration: In your Firebase project, go to the Firebase Console and click on "Project settings." Under the "General" tab, scroll down to the "Your apps" section and click on the "Firebase SDK snippet" icon (</>) for the web app. This will provide you with a configuration snippet containing your Firebase credentials.

  • Initialize Firebase in Flutter: In your Flutter app, open the main.dart file and add the following code to initialize Firebase using the configuration snippet obtained in the previous step:

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}

Building the Address Book App

Now, let's start building our address book app. We'll create a simple app with the following features:

  • Display a list of contacts.

  • Add a new contact.

  • Edit an existing contact.

  • Delete a contact.

Create a Firestore Collection

In Firestore, data is organized into collections and documents. For our address book app, let's create a collection named "contacts."

final CollectionReference contactsCollection = FirebaseFirestore.instance.collection('contacts');

Create a Model Class

We'll need a model class to represent our contact. Create a file named contact.dart and define the following class:

class Contact {
final String id;
final String name;
final String phoneNumber;

Contact({required this.id, required this.name, required this.phoneNumber});
}

Create a CRUD Service

Next, let's create a service to perform CRUD operations on our Firestore collection. Create a file named crud_service.dart and implement the following methods:

import 'package:cloud_firestore/cloud_firestore.dart';

class CrudService {
// Reference to the Firestore collection
final CollectionReference contactsCollection = FirebaseFirestore.instance.collection('contacts');

Future&lt;void&gt; addContact(String name, String phoneNumber) async {
await contactsCollection.add({'name': name, 'phoneNumber': phoneNumber});
}

Future&lt;void&gt; updateContact(String id, String name, String phoneNumber) async {
await contactsCollection.doc(id).update({'name': name, 'phoneNumber': phoneNumber});
}

Future&lt;void&gt; deleteContact(String id) async {
await contactsCollection.doc(id).delete();
}
}

Implementing UI

Now, let's create the user interface for our address book app using Flutter widgets. We'll create screens for listing contacts, adding a new contact, and editing an existing contact.

Listing Contacts

import 'package:flutter/material.dart';
import 'package:your_app_name/models/contact.dart';
import 'package:your_app_name/services/crud_service.dart';

class ContactListScreen extends StatelessWidget {
final CrudService crudService = CrudService();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Contacts')),
body: StreamBuilder&lt;QuerySnapshot&gt;(
stream: crudService.contactsCollection.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}

if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}

final contacts = snapshot.data?.docs ?? [];

return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = Contact(
id: contacts[index].id,
name: contacts[index]['name'],
phoneNumber: contacts[index]['phoneNumber'],
);

return ListTile(
title: Text(contact.name),
subtitle: Text(contact.phoneNumber),
onTap: () {
// Navigate to contact details/edit screen
},
onLongPress: () {
// Delete contact
},
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Navigate to add contact screen
},
child: Icon(Icons.add),
),
);
}
}

Adding and Editing Contacts

import 'package:flutter/material.dart';
import 'package:your_app_name/models/contact.dart';
import 'package:your_app_name/services/crud_service.dart';

class AddEditContactScreen extends StatefulWidget {
final Contact? contact;

AddEditContactScreen({this.contact});

@override
_AddEditContactScreenState createState() =&gt; _AddEditContactScreenState();
}

class _AddEditContactScreenState extends State&lt;AddEditContactScreen&gt; {
final CrudService crudService = CrudService();
final _formKey = GlobalKey&lt;FormState&gt;();
late TextEditingController _nameController;
late TextEditingController _phoneNumberController;

@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.contact?.name ?? '');
_phoneNumberController = TextEditingController(text: widget.contact?.phoneNumber ?? '');
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.contact == null ? 'Add Contact' : 'Edit Contact'),
),
body: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
return null;
},
),
TextFormField(
controller: _phoneNumberController,
decoration: InputDecoration(labelText: 'Phone Number'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a phone number';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
final name = _nameController.text;
final phoneNumber = _phoneNumberController.text;

if (widget.contact == null) {
// Add contact
} else {
// Update contact
}
}
},
child: Text(widget.contact == null ? 'Add Contact' : 'Save Changes'),
),
],
),
),
);
}
}

Conclusion

In this article, we've walked through the process of integrating Firestore into a Flutter app and building a complete CRUD address book application. You've learned how to set up Firestore, create a model class, implement CRUD operations, and create the user interface for listing, adding, and editing contacts.

This is just the beginning, and you can further enhance your app by adding authentication, search functionality, and more features. Firestore and Flutter provide a powerful combination for building modern and scalable mobile applications.

Happy coding!