Advanced Flutter Techniques Every Mobile Developer Should Know

Snehal Singh 👩‍💻
6 min readSep 17, 2024

--

Introduction

  • Flutter is the preferred tool for cross-platform development due to its adaptability and effectiveness, but there are a lot of sophisticated ways that can improve the functionality and performance of your project. This tutorial is intended for experienced mobile developers who want to advance their Flutter abilities.
  • Here are some key Flutter principles that can help you become a more proficient developer: from platform-specific code and performance optimisation to complex state management and unique animations.

1. Advanced State Management: Bloc vs. Riverpod vs. Provider

State management is a crucial part of any Flutter app. While Provider is often the go-to solution for smaller projects, more complex applications demand advanced solutions like Bloc and Riverpod.

  • Bloc (Business Logic Component): Bloc allows you to completely separate the UI layer from your business logic. This is particularly useful in large applications where clean code architecture is important. Bloc makes state management predictable, testable, and reusable. For example, with Cubit, you can manage simple states and monitor transitions using BlocObserver to track state changes globally.
class CounterCubit extends Cubit<int> {
CounterCubit(this.initialState) : super(initialState);
final int initialState;

void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
  • Riverpod: Riverpod improves upon Provider by offering a more robust and testable solution. Unlike Provider, Riverpod is not dependent on the Flutter widget tree, making it safer and easier to work with. Its lazy loading and error-handling capabilities make it ideal for handling complex app states.
final todoListProvider = NotifierProvider<TodoList, List<Todo>>(TodoList.new);
  • When to Use Which: For smaller apps, Provider is often sufficient. However, for enterprise-level applications or apps with complex business logic, Bloc or Riverpod might be better suited. Bloc excels in ensuring predictable state transitions, while Riverpod offers a more flexible and scalable state management approach.

2. Creating Custom Animations with Flutter’s AnimationController

Flutter’s built-in widgets like Hero and AnimatedContainer are great for simple animations, but for full control, you’ll need to use AnimationController. This gives you the power to create more complex and unique animations tailored to your app’s needs.

  • Building Complex Animations: AnimationController is the core of Flutter’s animation system. You can pair it with Tween and CurvedAnimation to define how values transition over time.
class CustomAnimation extends StatefulWidget {
@override
_CustomAnimationState createState() => _CustomAnimationState();
}

class _CustomAnimationState extends State<CustomAnimation> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;

@override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
_controller.forward();
super.initState();
}

@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: Container(width: 100, height: 100, color: Colors.blue),
);
}
}
  • Animating Across Screens: Use PageRouteBuilder to create custom transitions when navigating between screens. This can give your app a polished and professional feel.

3. Performance Optimization: Best Practices

When building large, complex apps, performance optimization is essential to ensure a smooth user experience. Flutter’s rendering pipeline is designed to be fast, but there are always ways to optimize further.

  • Understanding the Rendering Pipeline: Flutter uses a declarative approach, meaning widgets are rebuilt when their state changes. However, excessive rebuilding can cause performance issues. Use const constructors to reduce rebuilds for static widgets.
  • Avoiding Unnecessary Rebuilds: Tools like RepaintBoundary can help isolate parts of the UI that need to be rebuilt, preventing the entire widget tree from being rebuilt unnecessarily.
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Container(child: Text('This won’t repaint')),
);
}
  • Efficient List Rendering: For apps that need to display large datasets, always use ListView.builder() or GridView.builder() to ensure only the visible items are built. For more complex layouts, consider using SliverList and SliverGrid.
  • Network Optimization: Ensure you’re caching network responses and compressing images or other large data files using tools like dio or http.

4. Integrating Native Code with Platform Channels

Flutter’s platform channels enable developers to call native Android (Java/Kotlin) or iOS (Swift/Objective-C) code directly from Dart. This is particularly useful when you need access to platform-specific functionality that Flutter doesn’t expose natively.

  • Platform Channels Overview: Flutter uses platform channels to send messages between Dart and the native platform. This allows you to tap into native APIs or third-party SDKs that aren’t available in Flutter.
MethodChannel _channel = MethodChannel('com.example/native');

Future<void> getNativeData() async {
try {
final String result = await _channel.invokeMethod('getNativeData');
print(result);
} catch (e) {
print("Failed to get native data: $e");
}
}
  • Practical Example: Suppose you need to use a native feature like accessing the device’s battery level. You would write the native code for both Android and iOS and invoke it using a platform channel.

5. Flutter Web and Desktop: Expanding Beyond Mobile

Flutter isn’t just limited to mobile anymore; with Flutter Web and Flutter Desktop, you can build apps that run across all major platforms with a single codebase.

  • Flutter Web: When transitioning to web development, focus on adapting layouts to various screen sizes and browsers. Flutter Web provides full control over your app’s UI but requires attention to responsiveness.
  • Flutter Desktop: Although still evolving, Flutter Desktop allows you to create apps for Windows, macOS, and Linux. This opens up new possibilities for creating productivity and enterprise applications.
  • Practical Example: Create a responsive layout that works seamlessly on mobile, web, and desktop.

6. Plugin and Package Development

When existing packages don’t meet your needs, Flutter allows you to build your own plugins. Sharing these with the community can boost your app’s development and also help other developers.

  • Building a Plugin: A Flutter plugin consists of Dart code and optional platform-specific implementations for Android and iOS.
  • Publishing a Plugin: Once your plugin is ready, you can publish it on pub.dev and make it available for the community.

7. Flutter DevTools: Profiling, Debugging, and Memory Leak Detection

Flutter DevTools offers a suite of performance and debugging tools to help you identify bottlenecks and debug issues in real-time.

  • Widget Inspector: The widget inspector helps you visualize your app’s widget hierarchy and identify layout or rendering problems.
  • Performance Profiling: Using the Timeline feature, you can identify performance issues by analyzing frames, detecting jank, and optimizing how your app is rendering.
  • Memory Leak Detection:
    – Dart’s Garbage Collector: Flutter uses Dart’s garbage collector to free up memory for objects no longer in use. However, memory leaks occur when objects are accidentally retained in memory, even though they’re no longer needed.
    – Memory Analyzer in DevTools: The memory profiler in DevTools helps track memory usage, identifying patterns that could indicate a memory leak. It allows you to:
    –> Track object allocations over time.
    –> Take heap snapshots to see which objects are still in memory.
    –> Monitor when and how often garbage collection is triggered.
    – Practical Steps to Prevent Leaks:
    –> Avoid Retaining Global Variables: Be cautious with global variables, as these can sometimes lead to memory leaks if the state they hold isn’t properly disposed of.
    –> Always dispose of controllers, such as AnimationController, TextEditingController, and streams, once you no longer need them. Implement the dispose() method in stateful widgets to clean up resources.
@override
void dispose() {
_controller.dispose();
super.dispose();
}

Conclusion

Flutter is a powerful framework that can help you build cross-platform apps efficiently, but to truly leverage its potential, you need to go beyond the basics. By mastering advanced state management, creating custom animations, integrating native code, optimizing performance, and utilizing Flutter DevTools, you can build apps that are not only beautiful but also highly performant and scalable.

Whether you’re building a mobile app, a web app, or even a desktop app, these advanced Flutter techniques will help you level up your development skills and deliver superior user experiences.

Please Follow đź’™ and clap đź‘Ź, if you find the article useful. And get notified of my upcoming articles.

Follow me on Twitter: @imsnehalsingh and Github: Snehal Singh to hear about my projects.

--

--

Snehal Singh 👩‍💻

SDE - 2 at DhiWise | Flutter Developer đź’™ | Women Techmakers Ambassador | Technical Writer & Instructor at Udemy