Skip to main content

48 posts tagged with "Dart"

View All Tags

STATE MANAGEMENT IN FLUTTER: PROVIDER VS. BLOC VS. REDUX

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

State management is one of the most important concepts in Flutter app development. Managing state effectively can make your app more efficient, faster, and easier to maintain.

In this article, we'll explore three popular state management solutions in Flutter: Provider, BLoC, and Redux.

State Management in Flutter

In Flutter, state management refers to the way in which data is managed and updated within an app. In general, there are two types of state: local state and global state.

Local state is data that is used only within a single widget. For example, if you have a button that changes color when clicked, the color of the button is a piece of local state.

Global state is data that needs to be accessed by multiple widgets within the app. For example, if you have a shopping app and you need to keep track of the user's cart across multiple screens, the contents of the cart are global state.

1. Provider

Provider is a state management solution that was introduced as an alternative to Flutter's built-in setState() method. Provider is a relatively new solution but has gained popularity among developers because of its simplicity and ease of use.

Provider works by creating a central data store that can be accessed by any widget within the app. This data store is known as a ChangeNotifier and is responsible for managing the app's global state.

Here is an example of how to use Provider in Flutter:

class CartModel extends ChangeNotifier {
List<Item> _items = [];

List<Item> get items => _items;

void addItem(Item item) {
_items.add(item);
notifyListeners();
}
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CartModel(),
child: MaterialApp(
home: MyHomePage(),
),
);
}
}

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = Provider.of<CartModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: Center(
child: FlatButton(
onPressed: () {
cart.addItem(Item(name: 'Item 1', price: 10));
},
child: Text('Add to Cart'),
),
),
);
}
}

In this example, we create a CartModel class that manages the app's global state. We then wrap our MyApp widget in a ChangeNotifierProvider, which provides access to the CartModel to any widget within the app. Finally, in the MyHomePage widget, we use the Provider.of<CartModel>(context) method to access the CartModel and add items to the cart when the user clicks the "Add to Cart" button.

2. BLoC

BLoC (Business Logic Component) is another popular state management solution in Flutter. BLoC separates the business logic of the app from the user interface, making it easier to manage complex state.

BLoC works by creating a stream of data that emits events whenever the state changes. Widgets can then subscribe to this stream and update themselves accordingly.

Here is an example of how to use BLoC in Flutter:

class CartBloc {
final _cart = BehaviorSubject&lt;List&lt;Item&gt;&gt;.seeded([]);

Stream&lt;List&lt;Item&gt;&gt; get cart =&gt; _cart.stream;

void addItem(Item item) {
final items = _cart.value;
items.add(item);
_cart.add(items);
}

void dispose() {
_cart.close();
}
}

class MyApp extends StatelessWidget {
final cart = CartBloc();

@override
Widget build(BuildContext context) {
return StreamProvider&lt;List&lt;Item&gt;&gt;.value(
value: cart.cart,
initialData: [],
child: MaterialApp(
home: MyHomePage(),
),
);
}

@override
void dispose() {
cart.dispose();
super.dispose();
}
}

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = Provider.of&lt;List&lt;Item&gt;&gt;(context);

return Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: Center(
child: FlatButton(
onPressed: () {
Provider.of&lt;CartBloc&gt;(context, listen: false).addItem(Item(name: 'Item 1', price: 10));
},
child: Text('Add to Cart'),
),
),
);
}
}

In this example, we create a CartBloc class that manages the app's global state. We then use a StreamProvider to provide access to the cart stream to any widget within the app. Finally, in the MyHomePage widget, we use the Provider.of&lt;List&lt;Item&gt;&gt;(context) method to access the cart and add items to the cart when the user clicks the "Add to Cart" button.

3. Redux

Redux is a popular state management solution in the web development world, and has also gained popularity in the Flutter community. Redux works by creating a single data store that is responsible for managing the app's global state. This data store is modified by dispatching actions, which are then handled by reducers that update the state.

Here is an example of how to use Redux in Flutter:

enum CartAction { addItem }

class CartState {
final List&lt;Item&gt; items;

CartState({this.items});

CartState.initialState() : items = [];
}

CartState cartReducer(CartState state, dynamic action) {
if (action == CartAction.addItem) {
return CartState(items: List.from(state.items)..add(Item(name: 'Item 1', price: 10)));
}

return state;
}

class MyApp extends StatelessWidget {
final store = Store&lt;CartState&gt;(cartReducer, initialState: CartState.initialState());

@overrideWidget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(
home: MyHomePage(),
),
);
}
}

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: Center(
child: FlatButton(
onPressed: () {
StoreProvider.of&lt;CartState&gt;(context).dispatch(CartAction.addItem);
},
child: Text('Add to Cart'),
),
),
);
}
}

In this example, we create a CartState class that manages the app's global state. We then use a StoreProvider to provide access to the store to any widget within the app. Finally, in the MyHomePage widget, we use the StoreProvider.of<CartState>(context) method to access the store and dispatch an action to add an item to the cart when the user clicks the "Add to Cart" button.

Conclusion

There are several popular state management solutions in Flutter, including Provider, BLoC, and Redux. Each solution has its own strengths and weaknesses, and the best solution for your project will depend on a variety of factors, including the complexity of the app and the preferences of the development team.

When choosing a state management solution, it's important to consider factors such as the ease of use, the level of abstraction, the performance, and the scalability of the solution. It's also important to consider the trade-offs between different solutions in terms of code complexity, maintenance, and the ability to integrate with other tools and libraries.

Provider is a great choice for simple apps with straightforward state management needs. It is easy to use and has a low learning curve, making it a popular choice for beginners.

BLoC is a more complex solution that offers a high level of abstraction, making it a good choice for complex apps with complex state management needs.

Redux is a mature and battle-tested solution that is widely used in the web development world and offers excellent scalability and performance.

The best state management solution for your project will depend on a variety of factors, including the size and complexity of your app, your team's preferences and skill level, and your performance and scalability requirements.

Regardless of which solution you choose, it's important to follow best practices for state management, such as separating UI logic from business logic, minimizing unnecessary state changes, and keeping state management code as simple and modular as possible. With the right approach and the right tools, you can build robust and scalable Flutter apps that deliver great user experiences and meet your business goals.

GETTING STARTED WITH FLUTTER: A BEGINNER'S GUIDE

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

Flutter is an open-source mobile application development framework created by Google. It allows developers to build high-quality, natively compiled applications for mobile, web, and desktop from a single codebase. Flutter uses the Dart programming language, which was also created by Google, and provides a rich set of pre-built widgets, tools, and libraries to simplify the development process.

In this guide, we will cover the basics of getting started with Flutter, including setting up your development environment, creating a new project, and building a simple user interface.

Prerequisites

Before we get started, you'll need to have the following software installed on your computer:

  • Flutter SDK

  • Android Studio or Visual Studio Code (with the Flutter extension)

  • Xcode (if you're developing for iOS)

You can download the Flutter SDK from the official Flutter website: https://flutter.dev/docs/get-started/install

Creating a New Project

Once you have Flutter installed, you can create a new project by running the following command in your terminal:

flutter create my_app

This will create a new Flutter project named my_app in your current directory.

Running the App

To run the app, you'll need to have an emulator or a physical device connected to your computer. To start the app on an emulator, run the following command:

flutter run

This will build the app and launch it on the default emulator.

Building the User Interface

Flutter provides a wide range of pre-built widgets that you can use to build your app's user interface. In this example, we will create a simple app that displays a list of items.

First, open the lib/main.dart file in your project directory. This is the main entry point of your app.

Next, remove the existing code and replace it with the following:

import 'package:flutter/material.dart';

void main() =&gt; runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: ListView(
children: [
ListTile(
leading: Icon(Icons.ac_unit),
title: Text('Item 1'),
),
ListTile(
leading: Icon(Icons.access_alarm),
title: Text('Item 2'),
),
ListTile(
leading: Icon(Icons.accessibility),
title: Text('Item 3'),
),
],
),
),
);
}
}

Let's break down the code.

The import 'package:flutter/material.dart'; statement imports the Material package, which provides the MaterialApp widget that we'll use to define our app's theme and navigation structure.

The MyApp class is a stateless widget that defines the structure of our app. In this example, we've defined a simple MaterialApp with a Scaffold widget as its home screen.

The Scaffold widget provides a basic framework for our app's layout, including an app bar and a body. We've set the app bar's title to "My App" and the body to a ListView widget that displays a list of ListTile widgets.

Each ListTile widget displays an icon and a title. We've used three different icons (Icons.ac_unit, Icons.access_alarm, and Icons.accessibility) and three different titles ("Item 1", "Item 2", and "Item 3").

Conclusion

In this beginner's guide to Flutter, we covered the basics of setting up your development environment, creating a new project, and building a simple user interface. We used pre-built widgets from the Material package to create a basic layout, and we explored some of the basic concepts of Flutter app development.

As you continue to learn and explore Flutter, you'll discover many more powerful widgets, tools, and libraries that can help you create beautiful and highly functional apps. With its rich set of features and excellent documentation, Flutter is a great choice for developers who want to build high-quality, cross-platform applications quickly and efficiently.

CONCURRENCY AND PARALLELISM IN DART AND HOW IT IS USED IN FLUTTER

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

Concurrency and parallelism are essential concepts in programming that allow developers to optimize application performance and enhance user experience. In Dart, the programming language used in developing Flutter apps, concurrency and parallelism can be achieved using various mechanisms such as Isolates, Futures, and Streams. In this blog, we will discuss the basics of concurrency and parallelism in Dart, and how they can be used to improve the performance of Flutter apps.

What is Concurrency?

Concurrency is the ability of a system to run multiple tasks or processes simultaneously.

Isolates in Dart

In Dart, concurrency can be achieved through Isolates, which are Dart's lightweight units of concurrency that run in their own memory space, have their own event loop, and do not share memory with other isolates.

Isolates can communicate with each other through message passing, which involves sending and receiving messages between isolates. Isolates are designed to be safe and isolate the app's code from errors or bugs that may occur in other isolates. This means that if an isolate crashes, it will not affect the rest of the app or other isolates.

Isolates can be used to perform CPU-bound or long-running operations without blocking the UI thread or main isolate. This is important in Flutter apps, where long-running operations can cause the app to become unresponsive and affect the user experience.

To create an isolate in Dart, we can use the Isolate.spawn() method, which takes a function to be executed in the isolate as its argument. Here is an example:

import 'dart:isolate';

void main() async {
final receivePort = ReceivePort();
await Isolate.spawn(isolateFunction, receivePort.sendPort);
receivePort.listen((message) =&gt; print('Received: $message'));
}

void isolateFunction(SendPort sendPort) {
sendPort.send('Hello from isolate!');
}

In this example, we create a new isolate using the Isolate.spawn() method and pass a function called isolateFunction to be executed in the isolate. The receivePort is used to receive messages sent from the isolate, and we listen for incoming messages using the listen() method. When the isolate sends a message using the sendPort.send() method, it is received by the receivePort, and we print the message to the console.

What is Parallelism?

Parallelism is the ability of a system to execute multiple tasks or processes simultaneously on multiple processors or cores. In Dart, parallelism can be achieved through asynchronous programming using Futures and Streams.

Futures in Dart

Futures in Dart represent a value that may not be available yet but will be at some point in the future. Futures can be used to perform asynchronous operations such as network requests, file I/O, and other long-running operations that do not block the UI thread.

To use a Future in Dart, we can create a new instance of the Future class and pass a function that returns the value of the Future as its argument. Here is an example:

void main() {
final future = Future(() =&gt; 'Hello, world!');
future.then((value) =&gt; print(value));
}

In this example, we create a new Future using the Future() constructor and pass a function that returns the value 'Hello, world!' as its argument. We then use the then() method to listen for the completion of the Future and print its value to the console.

Streams in Dart

Streams in Dart represent a sequence of values that can be asynchronously produced and consumed. Streams can be used to perform asynchronous operations that produce a series of values such as user input, sensor data, and other real-time data.

To use a Stream in Dart, we can create a new instance of the Stream class and pass a function that produces the values of the Stream as its argument. Here is an example:

import 'dart:async';

void main() {
final stream = Stream.periodic(Duration(seconds: 1), (value) =&gt; value);
stream.listen((value) =&gt; print(value));
}

In this example, we create a new Stream using the Stream.periodic() constructor and pass a function that produces the value of the Stream as its argument. The function returns the value of a counter that increments by one every second. We then use the listen() method to listen for the values produced by the Stream and print them to the console.

Concurrency and Parallelism in Flutter

In Flutter, concurrency and parallelism can be used to improve the performance of the app and enhance the user experience. Here are some examples of how concurrency and parallelism can be used in Flutter:

  • Performing long-running operations: Long-running operations such as network requests, file I/O, and database queries can be performed in isolates or using Futures to avoid blocking the UI thread and improve app performance.
import 'dart:async';
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
Future&lt;String&gt; fetchData() async {
// perform long-running operation
return 'Hello, world!';
}

@override
Widget build(BuildContext context) {
return FutureBuilder&lt;String&gt;(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
);
}
}

In this example, we use a Future to perform a long-running operation that returns the value 'Hello, world!'. We then use a FutureBuilder widget to display the value returned by the Future when it is available.

  • Handling real-time data: Real-time data such as user input and sensor data can be handled using Streams to provide a responsive user experience.
import 'dart:async';
import 'package:flutter/material.dart';

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

class _MyWidgetState extends State&lt;MyWidget&gt; {
final _streamController = StreamController&lt;String&gt;();

@override
void dispose() {
_streamController.close();
super.dispose();
}

@override
Widget build(BuildContext context) {
return StreamBuilder&lt;String&gt;(
stream: _streamController.stream,
builder: (context, snapshot) {
return TextField(
onChanged: (value) =&gt; _streamController.add(value),
decoration: InputDecoration(
hintText: 'Enter text',
labelText: 'Text',
),
);
},
);
}
}

In this example, we use a StreamController to handle user input from a TextField widget. We then use a StreamBuilder widget to listen for the values produced by the Stream and update the UI when new values are available.

  • Isolates are an excellent tool for providing concurrency in Flutter apps. They allow developers to perform computationally intensive operations in the background without blocking the main UI thread, which can improve the app's performance and responsiveness.
import 'dart:isolate';

import 'package:flutter/material.dart';

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

class _MyWidgetState extends State&lt;MyWidget&gt; {
String _result = '';

@override
void initState() {
super.initState();
_calculate();
}

void _calculate() async {
final receivePort = ReceivePort();
final isolate = await Isolate.spawn(_compute, receivePort.sendPort);

receivePort.listen((message) {
setState(() {
_result = 'Result: $message';
});
receivePort.close();
isolate.kill();
});
}

static void _compute(SendPort sendPort) {
// Do some expensive computation here...
final result = 42;
sendPort.send(result);
}

@override
Widget build(BuildContext context) {
return Center(
child: Text(_result),
);
}
}

In this example, we create a StatefulWidget called MyWidget. In the initState() method, we call the _calculate() method to perform some expensive computation in an isolate.

The _calculate() method creates a ReceivePort and spawns an isolate using the Isolate.spawn() method. We pass the sendPort of the ReceivePort to the _compute() function in the isolate.

In the _compute() function, we perform some expensive computation and send the result back to the main isolate using the sendPort.send() method.

In the receivePort.listen() callback, we update the _result variable with the computed result and call setState() to update the UI. We also close the ReceivePort and kill the isolate.

Finally, in the build() method, we display the computed result in a Text widget in the center of the screen.

Note that isolates cannot access the BuildContext object directly, so we cannot use Scaffold.of(context) or Navigator.of(context) inside an isolate. However, we can pass arguments to the _compute() function using the Isolate.spawn() method if needed.

Conclusion

Concurrency and parallelism are essential concepts in programming that can be used to optimize application performance and enhance user experience. In Dart, concurrency can be achieved using Isolates, while parallelism can be achieved using Futures and Streams.

In Flutter, concurrency and parallelism can be used to perform long-running operations, handle real-time data, and improve app performance. Understanding these concepts and how to use them in Flutter can help developers create fast and responsive apps that provide an excellent user experience.