Current Path : /www/sites/www.coderblog.in/index/
Url:

NameSizeOptions
App_DataDIRnone
backupDIRnone
cgi-binDIRnone
cssDIRnone
imgDIRnone
metaDIRnone
wp-adminDIRnone
wp-contentDIRnone
wp-includesDIRnone
.htaccess3.35 KBDEL
.htaccess.bk1.84 KBDEL
404.html0.13 KBDEL
ads.txt1.07 KBDEL
favicon.ico2.19 KBDEL
index.php0.40 KBDEL
license.txt19.44 KBDEL
php.ini0.58 KBDEL
readme.html7.25 KBDEL
service.php0.00 KBDEL
web.config2.87 KBDEL
wp-activate.php7.21 KBDEL
wp-blog-header.php1.21 KBDEL
wp-comments-post.php2.27 KBDEL
wp-config-sample.php3.26 KBDEL
wp-config.php2.98 KBDEL
wp-cron.php5.49 KBDEL
wp-links-opml.php2.44 KBDEL
wp-load.php3.84 KBDEL
wp-login.php50.21 KBDEL
wp-mail.php8.52 KBDEL
wp-settings.php29.38 KBDEL
wp-signup.php33.71 KBDEL
wp-trackback.php4.98 KBDEL
xmlrpc.php3.13 KBDEL
Flutter – Coder Blog https://www.coderblog.in Join the coding revolution! Learn, share, and grow together! Thu, 22 Aug 2024 14:35:07 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.1 How to use implicit animations in Flutter https://www.coderblog.in/2024/08/how-to-use-implicit-animations-in-flutter/ https://www.coderblog.in/2024/08/how-to-use-implicit-animations-in-flutter/#comments Thu, 22 Aug 2024 14:29:49 +0000 https://www.coderblog.in/?p=1132 How to use implicit animations in Flutter  1. Introduction Many widgets can help manage the animation in Flutter,

<p>The post How to use implicit animations in Flutter first appeared on Coder Blog.</p>

]]>
How to use implicit animations in Flutter

 1. Introduction

Many widgets can help manage the animation in Flutter, and these widgets can be called implicit animation widgets. So the implicit animation widget starts with Animated in Flutter. These are pre-programmed animations where you don’t need to write the animation code yourself. Instead, you only need to change some properties or values.

I will introduce it in detail in the following article

2. Implicitly Animated Widget

All of the implicit animate widgets are inherited from ImplicitlyAnimatedWidget, please find the below codes for ImplicitlyAnimatedWidget

const ImplicitlyAnimatedWidget({
        super.key,
        this.curve = Curves.linear,
        required this.duration,
        this.onEnd,
});

1) curve

In Flutter, the Curves class provides many common animation curves (Curves), which define the speed mode of animation and control the movement of animation. The following are all enumeration values ​​​​in the Curves class:

No. Animation Type Description
1 linear linear animation
2 easeIn Animation that slowly starts to accelerate
3 easeOut Quickly start decelerating animation
4 easeInOut Animation that slowly starts to accelerate and then decelerates
5   decelerate slow down animation
6   bounceIn Animation bounce effect enters
7   bounceOut Animation bounce effect exits
8   bounceInOut Animated bounce effect entry and exit
9   elasticIn Animation elastic effect enters
10 elasticOut Animated elastic effect exits
11 elasticInOut Animated elastic effects entering and exiting
12 astOutSlowIn Animation enters quickly and exits slowly
13 slowMiddle Animation slow middle
14 ease Slow start and end, faster in the middle
15 easeInCirc Circular curve starts slowly
16 easeOutCirc The circular curve ends slowly
17 easeInOutCirc Circular curve starts and ends slowly
18 easeInCubic The cubic curve starts slowly
19 easeOutCubic The cubic curve ends slowly
20 easeInOutCubic Cubic curve starts and ends slowly
21 easeInExpo The exponential curve starts slowly
22 easeOutExpo The exponential curve slowly ends
23 easeInOutExpo The exponential curve starts and ends slowly
24 easeInQuad The quadratic curve starts slowly
25 easeOutQuad The quadratic curve ends slowly
26 easeInOutQuad Quadratic curve starts and ends slowly
27 easeInQuart The quartic curve starts slowly
28 easeOutQuart The quartic curve ends slowly
29 easeInOutQuart Quadric curve starts and ends slowly
30 easeInQuint Quintic curve starts slowly
31 easeOutQuint The quintic curve ends slowly
32 easeInOutQuint Quintic curve starts and ends slowly
33 easeInSine Sine curve starts slowly
34 easeOutSine The sine curve ends slowly
35 easeInOutSine Sine curve starts and ends slowly

Each curve has its own unique acceleration and deceleration patterns. Developers can choose the appropriate animation curve according to specific needs to achieve smooth animation effects. You can find more information about these curves in the official Flutter documentation: Flutter Curves Class

2) duration

The animation duration

3) onEnd

It will be called after the animation has been completed, you can trigger another animation with this parameter.

2. How to use

To use implicit animation, just use the widgets starting with Animated and change their properties value within state management then you will see the animations.

Please find the example below

2.1 Use the AnimatedContainer :

1) Define the variables of the animate properties

double _width = 200;
double _height = 200;
Color _color = Colors.red;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(16);

2) Create the widget with AnimatedContainer

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Animation controller'),
    ),
    body: Center(
      child: AnimatedContainer(
        width: _width,
        height: _height,
        decoration: BoxDecoration(
          color: _color,
          borderRadius: _borderRadius,
        ),
        // the duration of animation
        duration: const Duration(seconds: 1),
        // set the curve to let the animation more smooth
        curve: Curves.fastOutSlowIn,
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: _randomize,
      child: const Icon(Icons.play_arrow),
    ),
  );
}

3) Create the _randomize method to random to change the property values

final random = Random();

void _randomize() {
  setState(() {
    // generate random value with width & height
 _width = random.nextInt(300).toDouble();
 _height = random.nextInt(300).toDouble();
    // generate random color
 _color = Color.fromRGBO(
 random.nextInt(256),
 random.nextInt(256),
 random.nextInt(256),
      1,
 );
    // generate random border radius
 _borderRadius = BorderRadius.circular(random.nextInt(100).toDouble());
 });
}

and you will see the result below:

2.1 Use the AnimatedOpacity:

1) Define the property variable

double _opacity = 1.0;

2) Create the widget with AnimatedOpacity

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Fade In & Fade Out'),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          AnimatedOpacity(
            opacity: _opacity,
            duration: const Duration(seconds: 1),
            curve: Curves.fastOutSlowIn,
            child: const FlutterLogo(size: 200),
          ),
          ElevatedButton(
            onPressed: _changeOpacity,
            child: const Text('Fade In/Out'),
          ),
        ],
      ),
    ),
  );
}

3) Create the change method to update the opacity property

void _changeOpacity() {
    setState(() => _opacity = _opacity == 0 ? 1.0 : 0.0);
 }

and you will see the result below:

3. Animated widgets

You can find the below widgets starting with Animated, and these are implicit animation widgets, you can use them the same with AnimatedContainer and  AnimatedOpacity

No Widget                     Description                                                      
1   AnimatedOpacity Fades a widget in and out by changing its opacity.              
2   AnimatedContainer Animates properties of a container, such as size, color, and border.
3   AnimatedPositioned Animates the position of a child within a Stack widget.      
4   AnimatedAlign Animates the alignment of a child widget.                        
5   AnimatedBuilder A flexible widget that can be used to create custom animations.  
6   AnimatedPhysicalModel Animates properties of a physical model, such as elevation and shape.
7   AnimatedCrossFade Fades between two children, allowing for smooth transitions.      
8   AnimatedDefaultTextStyle Animates changes to the text style, such as font size and color.
9   AnimatedIcon Provides animated icons, such as transitions between states.    
10 AnimatedList A list that supports animation for adding or removing items.    
11 AnimatedSwitcher Smoothly transitions between multiple widgets based on a key.    
12 AnimatedPadding Animates changes in padding around a child widget.              
13 AnimatedSize Animates size changes, automatically adjusting to the new size.  

4. Custom implicit animation

If the above AnimatedWidget can’t fulfill your requirement, you can try to use TweenAnimationBuilder to create custom implicit animation.

4.1 The TwenAnimationBuilder constructor

First, we can take a look at the TwenAnimationBuilder constructor

TweenAnimationBuilder({
  Key? key,
  required Tween<T> tween,
  required Duration duration,
  required Widget Function(BuildContext, T, Widget?) builder,
  void Function()? onEnd,
  T Function()? onStatusChanged,
  Curve curve = Curves.linear,
  Widget? child,
}) 

The parameters as below

1) tween: A Tween object that defines the start and end values ​​of the animation.
2) duration: The duration of the animation.
3) builder: A callback function used to build the animation effect. This function is called during each frame of the animation.
4) onEnd: A callback function when the animation ends.
5) curve: Defines the curve of the animation progress.
6) child: A static child component that does not change with the animation to optimize performance.

4.2 How to use

We will create an animation effect that gradually changes color with a Flutter logo.

We define a variable to switch the initial value and end value of the gradient color. Tween is the abbreviation of Between. After we set the starting value of Between, Tween will automatically complete the color value switching. For color gradient, we use ColorTween to set the starting color value.

After clicking the button, change the starting value of the color to achieve the Tween animation effect.

1) Define a variable to change the color

bool changeRedToBlue = false;

2) Based on the variable to change the begin and end value in tween

tween: ColorTween(begin: changeRedToBlue?Colors.yellow:Colors.red, end: changeRedToBlue?Colors.red:Colors.yellow),

3) Change the variable in setState

onPressed: (){
  setState(() {
 changeRedToBlue =!changeRedToBlue;
 });
}

the complete codes as below

class TweenAnimationDemo extends StatefulWidget {
  const TweenAnimationDemo({super.key});

  @override
  State<TweenAnimationDemo> createState() =>
      _TweenAnimationDemoState();
}

class _TweenAnimationDemoState extends State<TweenAnimationDemo> {
  bool changeRedToBlue = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Tween Animation Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TweenAnimationBuilder<Color?>(
              tween: ColorTween(begin: changeRedToBlue?Colors.yellow:Colors.red, end: changeRedToBlue?Colors.red:Colors.yellow),
              duration: const Duration(seconds: 1),
              builder: (BuildContext context, Color? color, Widget? child) {
                return ColorFiltered(
                  colorFilter: ColorFilter.mode(color!, BlendMode.modulate),
                  child: child,
                );
              },
              child:const FlutterLogo(size: 200),
            ),
            const SizedBox(height: 50,),
            FilledButton(onPressed: (){
              setState(() {
                changeRedToBlue =!changeRedToBlue;
              });
            }, child: const Text('Toggle Color')),
          ],
        ),
      ),
    );
  }
}

and you will see the result below:

5. Conclusion

The implicit animation is easy to create, you can use the built-in widget to make the animations, and you can use different effects with curve, but you just can change the widget properties and a few controls, so it always forwards and no repeat. You can create a custom animation based on TweenAnimationBuilder, which can define the animation’s effect between start and end values. So if you want to create simple animations and less to control, implicit animation will be your choice.

Loading

<p>The post How to use implicit animations in Flutter first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/08/how-to-use-implicit-animations-in-flutter/feed/ 15
Use Drift for ORM in Flutter https://www.coderblog.in/2024/06/use-drift-for-orm-in-flutter/ https://www.coderblog.in/2024/06/use-drift-for-orm-in-flutter/#comments Fri, 14 Jun 2024 13:58:06 +0000 https://www.coderblog.in/?p=1173 1. Introduction Drift is a powerful database library for Dart and Flutter applications. To support its advanced capabilities

<p>The post Use Drift for ORM in Flutter first appeared on Coder Blog.</p>

]]>
1. Introduction

Drift is a powerful database library for Dart and Flutter applications. To support its advanced capabilities like type-safe SQL queries, verification of out database and migrations, it uses a builder and command-line tooling that runs at compile-time.

we can create the Dart class for mapping the table below

class TodoItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 6, max: 32)();
  TextColumn get content => text().named('body')();
  IntColumn get category => integer().nullable()();
}

But I like to use the SQL statement for generate the ORM coding (I will show we later). Because I will usually to create the SQL for a table first, and I just need to re-use my SQL will be ok 🙂

2. Usage

I will show we how to use the SQL statements to create a model in Drift.

2.1 Import dependencies

Drift is base on sqlite3 and flutter build runner for generating codes, so we need to import the below dependencies in pubspec.yaml file:

dependencies:
  drift: ^2.16.0
  sqlite3_flutter_libs: ^0.5.20
  path_provider: ^2.1.2
  path: ^1.9.0

dev_dependencies:
  drift_dev: ^2.16.0
  build_runner: ^2.4.8

2.2 Create the Database class

For example, we need to create a book db, so we can create a book_database.dart file below

import 'dart:io';

import 'package:drift/drift.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:drift/native.dart';

part 'book_database.g.dart'; //this is the generation file and need to use the same file name with existing db file

@DriftDatabase(
  include: {'book.drift'}, //the SQL file for create book table
)
class BookDatabase extends _$BookDatabase {
  BookDatabase() : super(_openConnection());
  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    // put the database file, called db.sqlite here, into the documents folder
    // for our app.
    final dbFolder = await getApplicationSupportDirectory();
    // setup the db file's path
    final file = File(p.join(dbFolder.path, 'book.db'));
    return NativeDatabase.createInBackground(file);
  });
}

2.2 Create the SQL statement file

Create the book.drift file, just put the normal SQL statements for creating the table, and we also can create the custom methods in SQL:

CREATE TABLE IF NOT EXISTS "book" (
            "id" INTEGER PRIMARY KEY NOT NULL UNIQUE,
            "viewCount" INTEGER DEFAULT(NULL),
            "name" VARCHAR(64) COLLATE NOCASE NOT NULL UNIQUE,
            "artist" VARCHAR(64) COLLATE NOCASE NOT NULL,
            "folder" VARCHAR(64) COLLATE NOCASE NOT NULL,
            "coverImg" VARCHAR(64) COLLATE NOCASE NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "book_1" ON book (id);
CREATE UNIQUE INDEX IF NOT EXISTS "book_2" ON book (name);

isBookExists: Select count(*) from book where name = :name Limit 1;

filterBooks: SELECT * FROM book WHERE $predicate;

getTop10: SELECT * FROM book ORDER BY viewCount DESC Limit 10;

As we can see, we just created a simple book table, and create the index for id and name column, in the other hand, we also create 3 custom methods, and I will let you know how to use them later.

After generate the code, drift will auto create these 3 methods in DB helper class with dart codes

2.3 Generate Database Helper Class

We can execute below command for generating DB helper class:

dart run build_runner build

we will find there is one more file will be generated book_database.g.dart, and we can also find the custom methods in it. Of course, we don’t need to care about how does it works, we just need to use it!

2.4 Using the Database Helper

We will use the GetX framework for the example.

put the BookDatabase class in main.dart file

Get.put(BookDatabase());

and we can use it in anywhere:

//get the book DB helper
final BookDatabase _bookDB = Get.find<BookDatabase>();

...

//get all book items from DB
var dbBooks = await _bookDB.book.select().get();

...

//only update the folder column in book item to DB
_bookDB.updateBookItem(
  bookItem.id,
  BookCompanion(
      folder: drift.Value(bookItem.folder),
  ),
);

...

// batch insert book items
List<BookData> dbBooks = [];
dbBooks.add(BookData(
        id: 1,
        viewCount: 0,
        name: 'book 1',
        artist: 'artist 1',
        folder: 'assets/books/01/',
        coverImg: 'assets/books/01/cover.jpg',));
dbBooks.add(BookData(
        id: 2,
        viewCount: 0,
        name: 'book 2',
        artist: 'artist 2',
        folder: 'assets/books/02/',
        coverImg: 'assets/books/02/cover.jpg',));

_bookDB.book.insertAll(dbBooks);

2.5 Use the custom methods

For this example, we have defined 3 methods in SQL statements:

isBookExists: Select count(*) from book where name = :name Limit 1 ;
filterBooks: SELECT * FROM book WHERE $predicate;
getTop10: SELECT * FROM book ORDER BY viewCount DESC Limit 10;

we can use them below

//check the book whether exists by name
int bookCount = await _bookDB.isBookExists('bookName').getSingle();

//get books with custom filter
var bookItems = await _bookDB.filterBooks((book) => book.name.contains('bookName')).get();

//get top 10 books
var topTenBooks = await _bookDB.getTop10().get();

3. Conclusion

Drift is useful for helping to manage the database, you can create the custom methods to get the data, and if you don’t want to use SQL statements, you also can use the Dart API and find the detail here. There are still many powerful functions you can find in the officer website.

Loading

<p>The post Use Drift for ORM in Flutter first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/06/use-drift-for-orm-in-flutter/feed/ 12
Create Flutter audio player in the background https://www.coderblog.in/2024/05/create-flutter-audio-player-in-the-background/ https://www.coderblog.in/2024/05/create-flutter-audio-player-in-the-background/#comments Thu, 16 May 2024 02:02:26 +0000 https://www.coderblog.in/?p=1160 1. Introduction There are several packages (e.g. assets_audio_player, audioplayers, just_audio) can handle audio play in Flutter, but if

<p>The post Create Flutter audio player in the background first appeared on Coder Blog.</p>

]]>
1. Introduction

There are several packages (e.g. assets_audio_player, audioplayers, just_audio) can handle audio play in Flutter, but if you want to play the audio in background, you need to do more. And I will show you how to do it in this article!

2. Preparation

We need to use the audio_service to handle the background task. audio_service does all the work to interface with Android, iOS, and other platforms so that you don’t need to worry about platform-related details, and  Audio Service isn’t an audio player; it’s just an interface to the system audio controls, so you still need another audio play packages for handle the player functions.

For this article, I will use just_audio to handle the audio service, you also can use other packages to do that. So we need to add the below in pubspec.yaml file:

dependencies:
    just_audio: ^0.9.36
    audio_service: ^0.18.12

The base flow should be

Flutter UI   -->   Audio Service   -->   Audio Handler

so we need to create an audio service and handler!

3. Create the Audio Handler

We can create the audio handler with just_audio first. The handler extends BaseAudioHandler and with QueueHandler

class JustAudioPlayerHandler extends BaseAudioHandler with QueueHandler {

    // create the just audio object and source for play list
    final _player = AudioPlayer();
    final _playlist = ConcatenatingAudioSource(children: []);

    //TODO other logics
    //...
}

We need to broadcast all playback state changes as they happen via playbackState within audio_service, so we can use _play.playbackEventStream to map the transform events and pipe to playbackState:

Transform a just_audio event into an audio_service state, this method should used from the constructor. Every event received from the just_audio player will be transformed into an audio_service state so that it can be broadcast to audio_service clients.

PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      // setup and allow which control item in the control panel in the phone's lock screen
      controls: [
        //MediaControl.skipToPrevious,
        MediaControl.rewind,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.fastForward,
        //MediaControl.skipToNext,
      ],
      // setup the action can be used, it will show the buttons in the phone's lock screen
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      // for android lock screen control buttons
      androidCompactActionIndices: const [0, 1, 3],
      // map the audio service processing state to just audio
      processingState: const {
        ProcessingState.idle: AudioProcessingState.idle,
        ProcessingState.loading: AudioProcessingState.loading,
        ProcessingState.buffering: AudioProcessingState.buffering,
        ProcessingState.ready: AudioProcessingState.ready,
        ProcessingState.completed: AudioProcessingState.completed,
      }[_player.processingState]!,
      playing: _player.playing, // is playing status
      updatePosition: _player.position,  // the current playing position
      bufferedPosition: _player.bufferedPosition,  // the buffered position
      speed: _player.speed,    // player speed
      queueIndex: event.currentIndex, // the index of the current queue
    );
  }

and We also need to load an empty playlist the first time to load

Future<void> _loadEmptyPlaylist() async {
  try {
    await _player.setAudioSource(_playlist);
  } catch (e) {
    print(e);
  }
}

and put these two init methods into the constructor

JustAudioPlayerHandler() {

    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);

    _loadEmptyPlaylist();
}

implement the base methods from the audio service

@override
Future<void> play() => _player.play();

@override
Future<void> pause() => _player.pause();

@override
Future<void> seek(Duration position) => _player.seek(position);

@override
Future<void> stop() async {
  await _player.stop();
  await playbackState.firstWhere(
      (state) => state.processingState == AudioProcessingState.idle);
}

@override
Future<void> addQueueItems(List<MediaItem> mediaItems) async {
  
  final audioSource = mediaItems.map(_createAudioSource);
  _playlist.addAll(audioSource.toList());
 
  final newQueue = queue.value..addAll(mediaItems);
  queue.add(newQueue);
}

// add the audio item into playlist and queue before playing it
@override
Future<void> addQueueItem(MediaItem mediaItem) async {
  
  final audioSource = _createAudioSource(mediaItem);
  _playlist.add(audioSource);
 
  final newQueue = queue.value..add(mediaItem);
  queue.add(newQueue);
}

@override
Future<void> removeQueueItemAt(int index) async {
  
  _playlist.removeAt(index);
 
  final newQueue = queue.value..removeAt(index);
  queue.add(newQueue);
}

@override
Future<dynamic> customAction(String name,
    [Map<String, dynamic>? extras]) async {
  // set the custom action like adjusting the volume from the UI
  if (name == 'setVolume') {
    _player.setVolume(extras!['value']);
  }
}

// create the just audio source from audio file
// we pass the audio file via mediaitem.id from UI
UriAudioSource _createAudioSource(MediaItem mediaItem) {
    print('add media item=========${mediaItem.id}');
    return AudioSource.uri(
      Uri.parse(mediaItem.id),
      tag: mediaItem,
    );
}

the base audio handler should be done, but you can also create some custom methods based on your requirements, for example, I want to get the audio’s total duration, then create the below method

Duration? getTotalDuration() => _player.duration;

and set the speed below

Future<void> setPlaySpeed(double speed) async {
    await _player.setSpeed(speed);
}

resume item

Future<Duration?> resumeMediaItem(
    MediaItem item, Duration currentDuration) async {
  var index = _getIndex(item);
  await _player.seek(currentDuration, index: index);
  mediaItem.add(item.copyWith(duration: _player.duration));
  await _player.play();
  return _player.duration;
}

// get the media item index in the playlist for seeking
// so that we can resume it
int _getIndex(MediaItem item) {
  int targetIndex = -1;
  final audioSource = _createAudioSource(item);
  for (int i = 0; i < _playlist.length; i++) {
    final currentItem = _playlist.children[i] as UriAudioSource;
    // print('current item');
    if (currentItem.uri.path == audioSource.uri.path) {
      //print('${audioSource.uri.path} index:$i');
      targetIndex = i;
      break;
    }
  }
  // print('get index:$targetIndex');
  return targetIndex;
}

check the item whether already exists in the playlist, this can avoid adding the duplicate item to the list

Future<bool> hasMediaItem(MediaItem item) async {
  var index = -1;
  if (_playlist.sequence.isNotEmpty) {
    index = _getIndex(item);
  }
  return index >= 0;
}

4. Create the Audio Service

After creating the audio handler, we can create the audio service and expose the methods to UI to call.

I will also use the GetX pattern for the example. So we can create a service that extends GetxService

class AudioPlayerService extends GetxService {

    // use the audio handler that we created
    late JustAudioPlayerHandler audioPlayerHandler;
    // define the MediaItem 
    late MediaItem _currentItem;
    // the current audio duration
    Duration _currentDuration = Duration.zero;    
}

initialize the audio handler in the init() event

void init() async {
    audioPlayerHandler = await AudioService.init(
      builder: () => JustAudioPlayerHandler(),
      config: const AudioServiceConfig(
        androidNotificationChannelId: 'com.audioplayer',  //set the app bundle id for android
        androidNotificationChannelName: 'Flutter Audio Player', //the name to show in the background player
        androidNotificationOngoing: true,
      ),
    );
}

implement the methods we defined in the handler

Future<void> addQueueItem(MediaItem item) async {
  _currentItem = item;
  await audioPlayerHandler.addQueueItem(item);
}

Future<bool> hasMediaItem(MediaItem item) async {
  return await audioPlayerHandler.hasMediaItem(item);
}

Future<void> setPlaySpeed(double speed) async {
  await audioPlayerHandler.setPlaySpeed(speed);
}

void setMediaItem(MediaItem item) {
  audioPlayerHandler.setMediaItem(item);
}

void setResumeMediaItem(MediaItem? item, Duration currentDuration) {
  // print('set resume duration:$currentDuration');
  if (item != null) {
    _currentItem = item;
  }
  _currentDuration = currentDuration;
}

Future<void> removeAll() async {
  audioPlayerHandler.removeAll();
}

Future<Duration?> getTotalDuration() async {
  return audioPlayerHandler.getTotalDuration();
}

Future<Duration?> play() =>
    audioPlayerHandler.resumeMediaItem(_currentItem, _currentDuration);

Future<void> pause() => audioPlayerHandler.pause();

Future<void> seek(Duration position) =>
    audioPlayerHandler.resumeMediaItem(_currentItem, position);

Future<void> stop() async {
  await audioPlayerHandler.stop();
}

Future<void> setVolume(double volume) async {
  await audioPlayerHandler.customAction('setVolume', {'value': volume});
}

The audio service will be simple, just call the methods from the handler.

5. Use it in the UI

Create a song model

For showing the song information on lock screen, we need the model below:

class SongItem {
    final int id;
    String name;
    String artist;
    String coverImg;
    String album;
    String audioFilePath;

    SongItem(this.id, this.name, this.artist, this.coverImg, this.album, this.audioFilePath);
}

and pass the model to the audio player service

Using the AudioPlayerService

We can easier to use the service via GetX, just put below in main.dart , and you can use the service anywhere

Get.find<AudioPlayerService>().init();

and we need to create a MediaItem from the song model

var mediaItem = MediaItem(
      id: songItem.audioFilePath,  //pass the audio file path to MediaItem's id
      album: songItem.album,
      title: songItem.name,
      artist: songItem.artist,
      artUri: Uri.file(songItem.coverImage),
    );

check the song whether has been adding to the playlist, if not then do it otherwise don’t add the duplicate item

//check and don't add the duplicate item
if (await audioPlayerService.hasMediaItem(mediaItem) &&
    resumeDuration.inMilliseconds > 0) {

  audioPlayerService.setResumeMediaItem(mediaItem, resumeDuration);
  currentDuration.value = resumeDuration.inMilliseconds.toDouble();
  playingItem(resumeDuration);

} else {
  audioPlayerService.addQueueItem(mediaItem);
}

listen to the duration for updating

AudioService.position.listen((Duration p) {
  
  currentDuration.value = p.inMilliseconds.toDouble();
  currentPosition.value =
      '${p.mmSSFormat} / ${totalDuration.value.mmSSFormat}';
  currentDuration.value = p.inMilliseconds.toDouble();
  resumeDuration = p;
  
  //TODO:
  // Update the progress bar values based on the current duration...
});

and create the base control methods for UI buttons

Future<void> play() async {
  audioPlayerService.setResumeMediaItem(null, resumeDuration);
  var totalDur = await audioPlayerService.play();
  //get the total duration 
  if (totalDur != null) {
    totalDuration.value = totalDur;
  }
  // handle other events...
}

Future<void> seek(Duration d) async {
  isPlaying.value = true;
  await audioPlayerService.seek(d);
}

Future<void> pause() async {
  isPlaying.value = false;
  await audioPlayerService.pause();
}

I just roughly explain how to use in the UI, there are still many things that need to be done for a complete UI logic, but these are not the main parts of this article 🙂

In the end, don’t forget setup the permissions of background audio service in Android and iOS

you can find the below link from audio_service settings:

Android  &  iOS

6. Conclusion

I just demoed how to play audio in the background with just_audio and audio_service, you also can create the audio handler with your favorite audio package (e.g assets_audio_player, audioplayers …). You also can make more controls in the transform event to make the lock screen look good. The customAction is the best way to handle your custom functions.

Ok, please let me know if you have other ideas?

If you enjoyed this article, please follow me here on Medium for more stories about .Net Core, Angular, and other Tech! 🙂

Loading

<p>The post Create Flutter audio player in the background first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/05/create-flutter-audio-player-in-the-background/feed/ 13
Use the powerful theme framework in GetX https://www.coderblog.in/2024/04/use-the-powerful-theme-framework-in-getx/ https://www.coderblog.in/2024/04/use-the-powerful-theme-framework-in-getx/#comments Mon, 08 Apr 2024 03:59:05 +0000 https://www.coderblog.in/?p=1145 1. Introduction FlexColorScheme is a very powerful theme framework for Flutter, there is complete documentation and tutorials, and

<p>The post Use the powerful theme framework in GetX first appeared on Coder Blog.</p>

]]>
1. Introduction

FlexColorScheme is a very powerful theme framework for Flutter, there is complete documentation and tutorials, and there are many existing themes that can be used, you also can design your theme online.

FlexColorScheme is at its core an advanced ThemeData factory. It makes customization of ThemeData simpler. It does not expose everything you can do with ThemeData via its own APIs. Its focus is on things that are complex and tedious to do with ThemeData. You can further tune the produced ThemeData by using copyWith and ThemeData’s own APIs.

The Themes Playground is a visual web configuration tool for the FlexColorScheme API. It allows you to play with different styles interactively, and copy the FlexColorScheme API setup code for the theme you created. The Playground does not cover all APIs offered by FlexColorScheme, but most of them, certainly the most commonly used ones.

2. Use in GetX

Because GetX can easy to help with dynamic change and handle the theme, I will base it on my previous article “Create Flutter project with GetX Pattern” to show you how to use FlexColorScheme in GetX.

2.1 Get the themes from FlexColorScheme

Create the theme folder under /lib/app/ui/, go to FlexColorScheme‘s playground to design or choose the existing theme, and click the Copy theme code button, then you can easy to copy the theme codes.

the theme code should be like below:

// Theme config for FlexColorScheme version 7.3.x. Make sure you use
// same or higher package version, but still same major version. If you
// use a lower package version, some properties may not be supported.
// In that case remove them after copying this theme to your app.
theme: FlexThemeData.light(
  scheme: FlexScheme.orangeM3,
  surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
  blendLevel: 7,
  subThemesData: const FlexSubThemesData(
    blendOnLevel: 10,
    blendOnColors: false,
    useTextTheme: true,
    useM2StyleDividerInM3: true,
    adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
    alignedDropdown: true,
    useInputDecoratorThemeInDialogs: true,
  ),
  visualDensity: FlexColorScheme.comfortablePlatformDensity,
  useMaterial3: true,
  swapLegacyOnMaterial3: true,
  // To use the Playground font, add GoogleFonts package and uncomment
  // fontFamily: GoogleFonts.notoSans().fontFamily,
),
darkTheme: FlexThemeData.dark(
  scheme: FlexScheme.orangeM3,
  surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
  blendLevel: 13,
  subThemesData: const FlexSubThemesData(
    blendOnLevel: 20,
    useTextTheme: true,
    useM2StyleDividerInM3: true,
    adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
    alignedDropdown: true,
    useInputDecoratorThemeInDialogs: true,
  ),
  visualDensity: FlexColorScheme.comfortablePlatformDensity,
  useMaterial3: true,
  swapLegacyOnMaterial3: true,
  // To use the Playground font, add GoogleFonts package and uncomment
  // fontFamily: GoogleFonts.notoSans().fontFamily,
),
// If you do not have a themeMode switch, uncomment this line
// to let the device system mode control the theme mode:
// themeMode: ThemeMode.system,

but we still need to change something, we need to create a class to handle each theme, for the above theme code, we can create the class ThemeBrown below

create a file /lib/app/ui/theme/theme_brown.dart and past the theme code:

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

class ThemeBrown {
  static ThemeData lightTheme() => FlexThemeData.light(
      scheme: FlexScheme.orangeM3,
      surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
      blendLevel: 7,
      subThemesData: const FlexSubThemesData(
        blendOnLevel: 10,
        blendOnColors: false,
        useTextTheme: true,
        useM2StyleDividerInM3: true,
        adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
        alignedDropdown: true,
        useInputDecoratorThemeInDialogs: true,
      ),
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      useMaterial3: true,
      swapLegacyOnMaterial3: true,
      // To use the Playground font, add GoogleFonts package and uncomment
      // fontFamily: GoogleFonts.notoSans().fontFamily,
    );

  static ThemeData darkTheme() => FlexThemeData.dark(
      scheme: FlexScheme.orangeM3,
      surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
      blendLevel: 13,
      subThemesData: const FlexSubThemesData(
        blendOnLevel: 20,
        useTextTheme: true,
        useM2StyleDividerInM3: true,
        adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
        alignedDropdown: true,
        useInputDecoratorThemeInDialogs: true,
      ),
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      useMaterial3: true,
      swapLegacyOnMaterial3: true,
      // To use the Playground font, add GoogleFonts package and uncomment
      // fontFamily: GoogleFonts.notoSans().fontFamily,
    );
}

as you can see, we put the light and dark theme to lightTheme and darkTheme method in ThemeBrown class, and then we can easy to use them later.

for example, I want to create 10 themes in my App, just repeat the above flow and create 10 theme classes and files

2.2 Create theme service

Because we need to handle multiple themes, we can create a service for handling the below theme logic:

  1. define the theme’s name
  2. get the theme by name
  3. get the current theme mode from storage

so the file should be /lib/app/data/service/theme_service.dart:

import 'package:get_storage/get_storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../ui/theme/theme_brown.dart';
import '../../ui/theme/theme_deepBlue.dart';
import '../../ui/theme/theme_gold.dart';
import '../../ui/theme/theme_green.dart';
import '../../ui/theme/theme_hippie_blue.dart';
import '../../ui/theme/theme_indigo.dart';
import '../../ui/theme/theme_orang.dart';
import '../../ui/theme/theme_purple.dart';
import '../../ui/theme/theme_red.dart';
import '../../ui/theme/theme_sakura.dart';

class ThemeService {
  //define each theme's name
  static const String Red = 'Red';
  static const String Indigo = 'Indigo';
  static const String HippieBlue = 'HippieBlue';
  static const String Green = 'Green';
  static const String DeepBlue = 'DeepBlue';
  static const String Sakura = 'Sakura';
  static const String Purple = 'Purple';
  static const String Brown = 'Brown';
  static const String Gold = 'Gold';
  static const String Orang = 'Orang';

  //use GetStorage to handle the current theme mode
  final _getStorage = GetStorage();
  final _storageKey = 'ThemeMode';
  static ThemeService instance = ThemeService._();
  // ignore: empty_constructor_bodies
  ThemeService._() {}
  set themeMode(ThemeMode themeMode) {
    if (themeMode == ThemeMode.system) {
      _getStorage.remove(_storageKey);
    } else {
      _getStorage.write(_storageKey, themeMode == ThemeMode.dark);
    }
    Get.changeThemeMode(themeMode);
  }

  ThemeMode get themeMode {
    switch (_getStorage.read(_storageKey)) {
      case true:
        return ThemeMode.dark;
      case false:
        return ThemeMode.light;
      default:
        return ThemeMode.system;
    }
  }

  //Get the theme by name and handle light and dark mode
  ThemeData getTheme(String name, {bool isDark = false}) {
    ThemeData currTheme = ThemeData();
    switch (name) {
      case Red:
        currTheme = isDark ? ThemeRed.darkTheme() : ThemeRed.lightTheme();
        break;
      case Indigo:
        currTheme = isDark ? ThemeIndigo.darkTheme() : ThemeIndigo.lightTheme();
        break;
      case HippieBlue:
        currTheme =
            isDark ? ThemeHippieBlue.darkTheme() : ThemeHippieBlue.lightTheme();
        break;
      case Green:
        currTheme = isDark ? ThemeGreen.darkTheme() : ThemeGreen.lightTheme();
        break;
      case DeepBlue:
        currTheme =
            isDark ? ThemeDeepBlue.darkTheme() : ThemeDeepBlue.lightTheme();
        break;
      case Sakura:
        currTheme = isDark ? ThemeSakura.darkTheme() : ThemeSakura.lightTheme();
        break;
      case Purple:
        currTheme = isDark ? ThemePurple.darkTheme() : ThemePurple.lightTheme();
        break;
      case Brown:
        currTheme = isDark ? ThemeBrown.darkTheme() : ThemeBrown.lightTheme();
        break;
      case Gold:
        currTheme = isDark ? ThemeGold.darkTheme() : ThemeGold.lightTheme();
        break;
      case Orang:
        currTheme = isDark ? ThemeOrang.darkTheme() : ThemeOrang.lightTheme();
        break;
    }

    return currTheme;
  }
}

2.3 Show the theme selection

We need to show the theme selection to the user to choose which theme they want to use, to simplify the operation, I use babstrap_settings_screen and hardcode to put the theme items in a ListView, of course you can use other way to present it.

below is only a code snippet for presenting one theme item with babstrap_settings_screen in ListView in UI page

/lib/app/ui/pages/settings_theme_page/settings_theme_page.dart

ListView(
  children: [
    SettingsGroup(
      items: [
        SettingsItem(
          onTap: () {
            controller.changeTheme(
                isDarkMode,
                ThemeService.Red,
                isDarkMode
                    ? ThemeRed.darkTheme()
                    : ThemeRed.lightTheme());
          },
          icons: CupertinoIcons.paintbrush_fill,
          iconStyle: IconStyle(
            iconsColor: Colors.white,
            withBackground: true,
            backgroundColor: Colors.red,
          ),
          title: LabelKeys.theme1.tr,
          trailing: Radio<String>(
            value: ThemeItem.Red,
            groupValue: controller.selectedValue.value,
            activeColor: Theme.of(context)
                .primaryColor, // Change the active radio button color here
            fillColor: MaterialStateProperty.all(Theme.of(context)
                .secondaryHeaderColor), // olor when selected
            splashRadius:
                20, // Change the splash radius when clicked
            onChanged: (value) {
              controller.changeTheme(
                  isDarkMode,
                  value!,
                  isDarkMode
                      ? ThemeRed.darkTheme()
                      : ThemeRed.lightTheme());
            },
          ),
        ),
      ],
    ),
  ],
)

and we can get the current theme mode by the below code

Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
bool isDarkMode = currentBrightness == Brightness.dark;

and handle the backend logic in controller

/lib/app/controllers/settings_theme_controller.dart

class SettingsThemeController extends GetxController {
  //define a variable to save selected theme value
  var selectedValue = Global.defaultTheme.obs;
  //use the local storage to save the selected theme
  var localStorage = Get.find<LocalStorageService>();

  @override
  void onReady() async {
    super.onReady();

    //get the current theme from local storage
    var currTheme =
        await localStorage.getValue<String>(LocalStorageKeys.currentTheme);

    //set the current theme
    selectedValue.value = currTheme!;
  }

  //change the theme when user click the item
  void changeTheme(bool isDarkMode, String name, ThemeData theme) {
    selectedValue.value = name;

    localStorage.setValue<String>(LocalStorageKeys.currentTheme, name);
    Get.changeThemeMode(isDarkMode ? ThemeMode.dark : ThemeMode.light);
    Get.changeTheme(theme);
  }
}

2.4 Handle default theme when App startup

In the end, we need to handle the default theme or selected theme when the app starts. So we need to update the main.dart file

If you create the GetX project like this article, you should get something like the below code in the main.dart

void main() async {

    WidgetsFlutterBinding.ensureInitialized();
    DependecyInjection.init();

    runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {

    return ScreenUtilInit(
      builder: (_, __) {
        return GetMaterialApp(
          title: 'Win_reading_harry_potter',
          debugShowCheckedModeBanner: false,
          translations: Translation(),
          initialBinding: AppBindings(),
          initialRoute: AppRoutes.SPLASH,
          unknownRoute: AppPages.unknownRoutePage,
          getPages: AppPages.pages,
          builder: (_, child) {
            return MainLayout(child: child!);
          },
        );
      },
      //! Must change it to true if you want to use the ScreenUtil
      designSize: const Size(411, 823),
    );
  }
}

we need to add some codes to support the theme

void main() async {

  ...

  //get the selected theme from local storage
  var localStorage = LocalStorageService();
  var currTheme =
      await localStorage.getValue<String>(LocalStorageKeys.currentTheme);

 //if there is no theme for first time, then use the default theme
 if (currTheme == null) {
    currTheme = Global.defaultTheme;
    localStorage.setValue<String>(
        LocalStorageKeys.currentTheme, Global.defaultTheme);
  }

  //pass the current theme to material app
  runApp(MyApp(currTheme: currTheme));
}

so also update the below

class MyApp extends StatelessWidget {
  //support the parameter for setting the current theme
  const MyApp({super.key, this.currTheme});

  final String? currTheme;

 @override
  Widget build(BuildContext context) {
    //get the light and dark theme base on current theme
    ThemeData lightTheme = currTheme == null
        ? ThemeDeepBlue.lightTheme()
        : ThemeItem.getTheme(currTheme!);
    ThemeData darkTheme = currTheme == null
        ? ThemeDeepBlue.darkTheme()
        : ThemeItem.getTheme(currTheme!, isDark: true);

    return ScreenUtilInit(
      builder: (_, __) {
        return GetMaterialApp(
          title: 'Win_reading_harry_potter',
          debugShowCheckedModeBanner: false,
          theme: lightTheme,    //set the light theme
          darkTheme: darkTheme, //set the dark theme
          themeMode: ThemeService.instance.themeMode, //set the default theme mode
          translations: Translation(),
          initialBinding: AppBindings(),
          initialRoute: AppRoutes.SPLASH,
          unknownRoute: AppPages.unknownRoutePage,
          getPages: AppPages.pages,
          builder: (_, child) {
            return MainLayout(child: child!);
          },
        );
      },
      //! Must change it to true if you want to use the ScreenUtil
      designSize: const Size(411, 823),
    );
  }
}

Done!

3. Conclusion

FlexColorScheme can help to make beautiful Flutter Material Design themes, and GetX and easy to handle and change the theme dynamic, so if you combine them, you will find that a great job!

Loading

<p>The post Use the powerful theme framework in GetX first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/04/use-the-powerful-theme-framework-in-getx/feed/ 20
Create Flutter project with GetX Pattern https://www.coderblog.in/2024/03/create-flutter-project-with-getx-pattern/ https://www.coderblog.in/2024/03/create-flutter-project-with-getx-pattern/#comments Tue, 19 Mar 2024 03:58:47 +0000 https://www.coderblog.in/?p=1139 1. Introduction In my previous article, I introduced the GetX framework, but there are several parts needed to

<p>The post Create Flutter project with GetX Pattern first appeared on Coder Blog.</p>

]]>
1. Introduction

In my previous article, I introduced the GetX framework, but there are several parts needed to create and handle in the GetX project, so today, let me show you how to create a good pattern with GetX project so that we can easier to use it 😃

2. The Architecture

A good architecture can help you organize your code and easy to maintain. There are many great architectures for Flutter projects, and you just need to find one of to suitable for you 🙂

I found the below architecture is suitable for me

the main structure is organized below folders:

/lib
    /app
        /bindings
        /config
        /controllers
        /data
        /routes
        /translation
        /ui
    mail.dart

GetX separates the view and controller just like the MVC pattern, so we can also put these in a difference folder for decoupling them.

/bindings      # GetX controller bindings
/controllers   # GetX controller
/routes        # GetX routes
/ui            # GetX views and layouts

And below folders for other things:

/config        # The global config or app information into `/config` folder. 
/data          # Handle database and some core logics
/translation   # Translation for support multiple languages in your project

The details of /data folder

/data
    /database    # Database handler or logic
    /failures    # Handle the failures logic for calling API 
    /helpers     # Helper methods of data handler
    /models      # The models of the project 
    /provider    # The providers of the project
    /services    # The services of the project

The details of /ui folder

/ui
    /global_widgets    # The global widgets
    /layouts           # If you want to handle different layouts in your project, you can use it
    /pages             # The UI presentation, like the viewer in MVC
    /theme             # The theme data or style
    /utils             # The utility methods

Of course, you may not need to use all of these folders in your actual project, just based on what you need.

3. Create the GetX project structure

Next, we need to create these folder structures. But it would be troublesome to create these manually every time, so we can use the VS Code extension: GetX Helper Awesome

Input the below command in VS Code, it will help to create and init the above project structure:

It not only creates the folder structure but also helps to create the GetX structure and codes. It will update your main.dart and use ScreenUtilInit to make a responsive app

You can use the below commands to create other items of the structure

but it will generate the Model and Respoistory based on Firestore, so if you don’t use Firestore library, you need to modify your codes after generating. By the way, there is a little bug in generating the route, it will miss a single quote in the route’s name.

3.1 Command Layout

There is a layout concern with this pattern, I think this is good for handler multiple layout cases. (e.g the login page and user page will have difference common layouts)

You can find the below main_layout.dart file after the init project structure in /ui/layouts folder

and other pages will be based on this main layout, for example, the home_page.dart in /ui/pages/home_page folder

as you can see, you can see different common layouts for each page. The folder structure would be below

4. Conclusion

A good architecture and project structure are critical in Flutter development to enhance maintainability, scalability, code reusability, testability, collaboration, and performance. It sets a solid foundation for building high-quality apps that can evolve, scale, and be maintained effectively over time. So you should find a suitable structure for yourself, and use the VS Code extension can quick and easy to do that 😁

Loading

<p>The post Create Flutter project with GetX Pattern first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/03/create-flutter-project-with-getx-pattern/feed/ 14
How to listen the app lifecycle event after Flutter 3.13 https://www.coderblog.in/2024/02/how-to-listen-the-app-lifecycle-event-after-flutter-3-13/ https://www.coderblog.in/2024/02/how-to-listen-the-app-lifecycle-event-after-flutter-3-13/#comments Wed, 21 Feb 2024 14:45:43 +0000 https://www.coderblog.in/?p=1125 1. What’s the App lifecycle state in Flutter? In Flutter, there are several lifecycle events that you can

<p>The post How to listen the app lifecycle event after Flutter 3.13 first appeared on Coder Blog.</p>

]]>
1. What’s the App lifecycle state in Flutter?

In Flutter, there are several lifecycle events that you can listen to in order to handle different states of your app, but today, we will discuss the didChangeAppLifecycleState events. This event is triggered whenever the app’s lifecycle state changes. The possible states are resumed, inactive, paused, detached and hidden. You can listen to this event using the WidgetsBindingObserver mixin.

  1. resumed: On all platforms, this state indicates that the application is in the default running mode for a running application that has input focus and is visible.

  2. inactive: At least one view of the application is visible, but none have input focus. The application is otherwise running normally.

  3. paused: The application is not currently visible to the user, and not responding to user input.

  4. detached: The application is still hosted by a Flutter engine but is detached from any host views.

  5. hidden: All views of an application are hidden, either because the application is about to be paused (on iOS and Android), or because it has been minimized or placed on a desktop that is no longer visible (on non-web desktop), or is running in a window or tab that is no longer visible (on the web).

By implementing these lifecycle states in your stateful widget, you can respond to different events and manage the state of your app accordingly. For example, you can pause or resume certain operations when the app goes into the background or handle data fetching when the widget’s dependencies change.

2. Listen to the App lifecycle events before Flutter 3.13

In versions of Flutter before 3.13, you could handle app lifecycle events by utilizing the WidgetsBindingObserver mixin. To do this, you would include the WidgetsBindingObserver mixin in your State class and override the didChangeAppLifecycleState method. Within this method, you could access the current state of the app (AppLifecycleState) and respond accordingly to different app lifecycle events.

class AppLifecyclePageOld extends StatefulWidget {
  const AppLifecyclePageOld({super.key});

  @override
  State<AppLifecyclePageOld> createState() => _AppLifecyclePageOldState();
}

class _AppLifecyclePageOldState extends State<AppLifecyclePageOld>
    // Use the WidgetsBindingObserver mixin
    with WidgetsBindingObserver {

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

    // Register your State class as a binding observer
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    // Unregister your State class as a binding observer
    WidgetsBinding.instance.removeObserver(this);

    super.dispose();
  }

  // Override the didChangeAppLifecycleState method and
  //Listen to the app lifecycle state changes
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    switch (state) {
      case AppLifecycleState.detached:
        _onDetached();
      case AppLifecycleState.resumed:
        _onResumed();
      case AppLifecycleState.inactive:
        _onInactive();
      case AppLifecycleState.hidden:
        _onHidden();
      case AppLifecycleState.paused:
        _onPaused();
    }
  }

  void _onDetached() => print('detached');

  void _onResumed() => print('resumed');

  void _onInactive() => print('inactive');

  void _onHidden() => print('hidden');

  void _onPaused() => print('paused');

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Placeholder(),
    );
  }
}

3. The new way to listen the App lifecycle event after Flutter 3.13

After Flutter 3.13, we can listen to the app lifecycle events using the new AppLifecycleListener class.

The AppLifecycleListener class provides a convenient and alternative approach for listening to app lifecycle events in Flutter. Instead of directly using the WidgetsBindingObserver mixin, you can utilize the AppLifecycleListener class to simplify the process.

To use AppLifecycleListener, create an instance of the class and pass the desired event callbacks that you want to listen to. This allows you to easily handle specific app lifecycle events without the need to implement the entire WidgetsBindingObserver mixin.

By using AppLifecycleListener, you can streamline your code and make it more readable and maintainable, as you only need to focus on the specific events you are interested in.

class AppLifecyclePage extends StatefulWidget {
  const AppLifecyclePage({super.key});

  @override
  State<AppLifecyclePage> createState() => _AppLifecyclePageState();
}

class _AppLifecyclePageState extends State<AppLifecyclePage> {
  late final AppLifecycleListener _listener;

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

    // Initialize the AppLifecycleListener class and pass callbacks
    _listener = AppLifecycleListener(
      onStateChange: _onStateChanged,
    );
  }

  @override
  void dispose() {
    // Do not forget to dispose the listener
    _listener.dispose();

    super.dispose();
  }

  // Listen to the app lifecycle state changes
  void _onStateChanged(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.detached:
        _onDetached();
      case AppLifecycleState.resumed:
        _onResumed();
      case AppLifecycleState.inactive:
        _onInactive();
      case AppLifecycleState.hidden:
        _onHidden();
      case AppLifecycleState.paused:
        _onPaused();
    }
  }

  void _onDetached() => print('detached');

  void _onResumed() => print('resumed');

  void _onInactive() => print('inactive');

  void _onHidden() => print('hidden');

  void _onPaused() => print('paused');

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Placeholder(),
    );
  }
}

4. What’s the difference?

The old way is very similar with the new way, to understand the main benefit of the AppLifecycleListener class, let’s take a look at the state machine diagram of the Flutter app lifecycle:

The diagram illustrates the various states of a Flutter app and the possible transitions between them. In the “old” approach, when overriding the didChangeAppLifecycleState method, you could only listen for the specific state changes, such as when the app transitioned to the resumed state. However, you were unable to capture information about the transitions between states. For example, you couldn’t determine if the app transitioned to the resumed state from the inactive or detached state.

With the introduction of the AppLifecycleListener class, you now have the capability to listen to these state transitions. This means you can track and respond to the sequence of states your app goes through, gaining a more comprehensive understanding of its lifecycle.

By leveraging the AppLifecycleListener class, you can effectively capture and handle the transitions between states, allowing for more precise control and customization of your app’s behavior.

class AppLifecyclePage extends StatefulWidget {
  const AppLifecyclePage({super.key});

  @override
  State<AppLifecyclePage> createState() => _AppLifecyclePageState();
}

class _AppLifecyclePageState extends State<AppLifecyclePage> {
  late final AppLifecycleListener _listener;
  String _currentState = '';

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

    // Pass all the callbacks for the transitions you want to listen to
    _listener = AppLifecycleListener(
      onDetach: _onDetach,
      onHide: _onHide,
      onInactive: _onInactive,
      onPause: _onPause,
      onRestart: _onRestart,
      onResume: _onResume,
      onShow: _onShow,
      onStateChange: _onStateChanged,
    );
  }

  @override
  void dispose() {
    _listener.dispose();

    super.dispose();
  }

  void _onDetach() {
    print('onDetach');
    _currentState = 'onDetach';
  }

  void _onHide() {
    print('onHide');
    _currentState = 'onHide';
  }

  void _onInactive() {
    print('onInactive');
    _currentState = 'onInactive';
  }

  void _onPause() {
    print('onPause');
    _currentState = 'onPause';
  }

  void _onRestart() {
    print('onRestart');
    _currentState = 'onRestart';
  }

  void _onResume() {
    print('onResume');
    _currentState = 'onResume';
  }

  void _onStateChanged(AppLifecycleState state) {
    // Track state changes
    if (_currentState == 'onInactive' && state == AppLifecycleState.resumed) {
      //to do something...
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Placeholder(),
    );
  }
}

The other benefit is that you don’t need to implement WidgetsBindingObserver mixin, this will be greatly convenient for the cases with complex parent classes. In that case, you may need to implement WidgetsBindingObserver mixin in your parent class and pass data between them to handle the lifecycle event, but for now, you can do that anywhere you want!

5. Cancel the App exit action

You can cancel the App exit using AppLifecycleListener class. There is a callback even onExitRequested in AppLifecycleListener, this callback is used to ask the application if it will allow exiting the application for cases where the exit is cancelable. For instance, it could be used for MacOS applications where the user tries to close the app when there are unsaved changes.

To cancel the exit request, you need to return AppExitResponse.cancel from the onExitRequested callback. Otherwise, return AppExitResponse.exit to allow the application to exit:

class AppLifecyclePage extends StatefulWidget {
  const AppLifecyclePage({super.key});

  @override
  State<AppLifecyclePage> createState() => _AppLifecyclePageState();
}

class _AppLifecyclePageState extends State<AppLifecyclePage> {
  late final AppLifecycleListener _listener;

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

    _listener = AppLifecycleListener(
      // Handle the onExitRequested callback
      onExitRequested: _onExitRequested,
    );
  }

  @override
  void dispose() {
    _listener.dispose();

    super.dispose();
  }

  // Ask the user if they want to exit the app. If the user
  // cancels the exit, return AppExitResponse.cancel. Otherwise,
  // return AppExitResponse.exit.
  Future<AppExitResponse> _onExitRequested() async {
    final response = await showDialog<AppExitResponse>(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog.adaptive(
        title: const Text('Are you sure you want to quit this app?'),
        content: const Text('All unsaved progress will be lost.'),
        actions: [
          TextButton(
            child: const Text('Cancel'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.cancel);
            },
          ),
          TextButton(
            child: const Text('Ok'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.exit);
            },
          ),
        ],
      ),
    );

    return response ?? AppExitResponse.exit;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('App Lifecycle Demo'),
      ),
      body: Center(
        child: Text(
          'Quit the App',
          style: Theme.of(context).textTheme.displayLarge,
        ),
      ),
    );
  }
}

The alert dialog will be shown when the user clicks the close button in the App.

6. Conclusion

Indeed, the AppLifecycleListener class introduces a fresh approach to listening to app lifecycle states, with a particular focus on capturing the transitions between these states. One notable feature of the AppLifecycleListener class is the inclusion of the onExitRequested callback. This callback streamlines the handling of exit requests, especially in scenarios where the exit can be canceled.

By utilizing the AppLifecycleListener class, you gain the ability to effectively monitor and respond to both the individual app lifecycle states and the transitions between them. Furthermore, the onExitRequested callback simplifies the management of exit requests, allowing for smoother cancellation or execution of the exit process based on your app’s specific requirements.

This streamlined approach to handling exit requests alleviates the burden of managing such scenarios and enhances the overall control and flexibility of your app’s lifecycle management.

Loading

<p>The post How to listen the app lifecycle event after Flutter 3.13 first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/02/how-to-listen-the-app-lifecycle-event-after-flutter-3-13/feed/ 9
How to create an internal HTTP server via Flutter for uploading file https://www.coderblog.in/2024/01/how-to-create-an-internal-http-server-via-flutter-for-uploading-file/ https://www.coderblog.in/2024/01/how-to-create-an-internal-http-server-via-flutter-for-uploading-file/#comments Mon, 08 Jan 2024 06:16:46 +0000 https://www.coderblog.in/?p=1117 1. Introduction If you want to let user upload files to the app through the browser on their

<p>The post How to create an internal HTTP server via Flutter for uploading file first appeared on Coder Blog.</p>

]]>
1. Introduction

If you want to let user upload files to the app through the browser on their computer, you can create an internal HTTP server with Flutter. This function is very useful, for example, if your app is just for watching videos or playing music offline, users can upload whatever they want.

2. Just do it

There are many issues we need to handle, but for now, I will only describe several main issues in this article, don’t worry, I will also provide the complete project’s source code at the end:

Ok, let’s solve the below main issues:

1) Create the HTTP server

We can use HttpServer to create the HTTP server and use bind method to set the IP and port

final s = await HttpServer.bind(address, port);

and listen to the request to handle any accesses

s.listen(_handleReq);

 _handleReq(HttpRequest request) async {
    ...
 }

But the next question is how can we get the IP address and pass it to the HttpServer ?

We can list all existing networks with NetworkInterface and filter the IP address type with IPv4

for (var interface in await NetworkInterface.list()) {
  for (var addr in interface.addresses) {
    if (addr.type == InternetAddressType.IPv4) {
      currentIP = addr.address;
    }
  }
}

but you will find that maybe there is more than one IP address in the list and only one is correct, so we also need to filter the IP starting with 192 to make sure this is the correct internal IP. This is working well in Android, but in iOS, maybe there is not only one IP start with 192, so we also need to check the network interface’s name whether is start with en, so the codes should be as below

 for (var interface in await NetworkInterface.list()) {
  for (var addr in interface.addresses) {
    if (addr.type == InternetAddressType.IPv4 &&
        (Platform.isIOS ? interface.name.startsWith('en') : 1 == 1) &&
        addr.address.startsWith('192')) {
      currentIP = addr.address;
    }
  }
}

2) Run the HTML files in HTTP server

We will put the HTML files in the server for the frontend layout, so we need to handle how to resolve the HTML files. You can put the HTML website in asset folder and update the pubspec.yaml

flutter:
  assets:
    - assets/
    - assets/webserver/
    - assets/webserver/css/
    - assets/webserver/fonts/
    - assets/webserver/js/

the HTML webserver folder structure is as below:

assets
    \webserver
        \css
        \fonts
        \js
        index.html        

and then create a Map object to save the requested HTML files

import 'dart:typed_data';

class AssetsCache {
  /// Assets cache
  static Map<String, ByteData> assets = {};

  /// Clears assets cache
  static void clear() {
    assets = {};
  }
}

Based on the above code, we need to handle the listen even with _handleReq function:

first, get the request URL for the current access file

_handleReq(HttpRequest request) async {
    String path = request.requestedUri.path.replaceFirst('/', '');
    ...
}

the above code will get the current access file from the URL. For example the link is below:

first request url:  http://192.168.1.215:8080/index.html
second request url: http://192.168.1.215:8080/js/index.js
...

the first request path value will be index.html, and the second request path value will be js/index.js, so we can create a _loadAsset function to handle it, the function will return the actual request file from webserver folder:

Future<ByteData> _loadAsset(String path) async {
    if (AssetsCache.assets.containsKey(path)) {
      return AssetsCache.assets[path]!;
    }

    if (_rootDir == null) {
      // print('path=============');
      // print(join(assetsBasePath, path));
      ByteData data = await rootBundle.load(join(assetsBasePath, path));
      return data;
    }

    if (await Directory(_rootDir.path).exists()) {}

    debugPrint(join(_rootDir.path, path));
    final f = File(join(_rootDir.path, path));
    return (await f.readAsBytes()).buffer.asByteData();
}

and respond the file on client side

final data = await _loadAsset(path);

request.response.headers.add('Content-Type', '$mime; charset=utf-8');
request.response.add(data.buffer.asUint8List());
request.response.close();

after that, you should access your HTML website in the HTTP server.

3) Upload the file from HTML to Flutter

You can use any ajax method to upload the file from the client, but for my case, I use the jquery file upload plugin, I would not show the details for the client side, just show you the core codes for how to handle in server side (Flutter).

After using ajax to post the file to the server for uploading, we can get the file in flutter by MimeMultipartTransformer

var transformer = MimeMultipartTransformer(
          request.headers.contentType!.parameters['boundary']!);

var bodyStream = transformer.bind(request);

//get the file object
await for (var part in bodyStream) {

  if (isFirstPart) {
    isFirstPart = false;
  } else {
    //because we want to support the larger file, so need to split the file into many chunks for uploading, so need to remove other chunks' content-dispostion and just keep one should be ok
    part.headers.remove('content-disposition');
  }
  var fileByte =
      await part.fold<List<int>>(<int>[], (b, d) => b..addAll(d));
  if (fileByte.length > 1) {
    //just need to handle not zero size file chunk
    uploadFile.add(fileByte);
    await Future.delayed(Duration.zero);
  }
}    

and then return the uploading status

final response = {
  'message': 'File uploadeding',
};
request.response
  ..statusCode = HttpStatus.ok
  ..headers.contentType = ContentType.json
  ..write(json.encode(response))
  ..close();

you can find more detailed codes here

3. Using the internal_http_server package

Alright, if you don’t want to code yourself, you can just use my internal_http_server package 🙂

3.1 Install the package

add the below into your pubspec.yaml

internal_http_server: ^0.0.3

3.2 Create the HTTP server

You need to create the server below

InternalHttpServer server = InternalHttpServer(
    title: 'Testing Web Server',
    address: InternetAddress.anyIPv4,
    port: 8080,
    logger: const DebugLogger(),
  );

3.3 Setup the description in your web server

You can set some descriptions in your web server, which will let users know what they can do or how they can do it with your server, for example, you want to let users upload the files

final String server_description = ''' Description:
        <ul>
          <li>
            You can put your <strong>webserver</strong> description here 
          </li>
          <li>
          It's support any <strong style='color:red'>HTML</strong>, you can describe what you want to say
          </li>
        </ul>

        How to use:
        <ul>
          <li>1. You can drag and drop the file here or click the 'Upload File' button  to upload</li>
          <li>2. It's support larger file</li>
          <li>3. You can upload multiple files once time</li>
        </ul>
       ''';

server.setDescription(server_description);

3.4 Create the start and stop server method

It needs to create the start and stop methods for the server

startServer() async {
  server.serve().then((value) {
    setState(() {
      isListening = true;
      buttonLabel = 'Stop';
    });
  });
}

stopServer() async {
  server.stop().then((value) {
    setState(() {
      isListening = false;
      buttonLabel = 'Start';
    });
  });
}

You can create a button to start and stop the server

ElevatedButton(
  child: Text(buttonLabel),
  onPressed: () {
    if (isListening) {
      stopServer();
    } else {
      startServer();
    }
  },
),

also, you need to let the user know the IP address of your web server, so you should get the device IP as below and show the IP in your APP, use the NetworkInterface to get the current network in the device

Future<String> getCurrentIP() async {
  // Getting WIFI IP Details
  String currentIP = '';
  try {
    for (var interface in await NetworkInterface.list()) {
      for (var addr in interface.addresses) {
        print(
            'Name: ${interface.name}  IP Address: ${addr.address}  IPV4: ${InternetAddress.anyIPv4}');
        if (addr.type == InternetAddressType.IPv4 &&
            addr.address.startsWith('192')) {
          currentIP = addr.address;
        }
      }
    }
  } catch (e) {
    debugPrint(e.toString());
  }
  // print('currentIP========: $currentIP');
  return currentIP;
}

After that, you should show the IP for the user like the below screen

and access the IP from the PC browser, you will find the website below, you can drag and drop the file to upload it

In the end, you can find the complete example here.

4. Conclusion

If you want to create the internal HTTP server in your app, there are still many technical issues that need to be solved, so I created a package for that, you can easily and for free to use, but I think there are still many things need to enhance of the package, grateful if you would help to enhance it 🙂

Loading

<p>The post How to create an internal HTTP server via Flutter for uploading file first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2024/01/how-to-create-an-internal-http-server-via-flutter-for-uploading-file/feed/ 10
How the GetX easy to use in Flutter https://www.coderblog.in/2023/12/how-the-getx-easy-to-use-in-flutter/ https://www.coderblog.in/2023/12/how-the-getx-easy-to-use-in-flutter/#comments Mon, 11 Dec 2023 02:44:28 +0000 https://www.coderblog.in/?p=1112 1. Introduction Flutter is a powerful and cross-platform mobile app development framework, it’s easy to learn and build

<p>The post How the GetX easy to use in Flutter first appeared on Coder Blog.</p>

]]>
1. Introduction

Flutter is a powerful and cross-platform mobile app development framework, it’s easy to learn and build a nice user interface. But, if you are an experienced c#/Java programmer (it’s me~ ha ha), maybe you will be a little uncomfortable, because Flutter is a reactive programming framework, reactive programming is a declarative programming paradigm that is based on the idea of asynchronous event processing and data streams.

So, if you want to update the UI in Flutter, you need to update the state to refresh the layout, the programming logic will be different with C# or Java, so luckily there are many state management frameworks to help do that, but these frameworks are not easy to understand and use except GetX.

GetX is a fast, stable, and light state management library in Flutter. There are so many State Management libraries in Flutter like MobX, BLoC, Redux, Provider, etc. GetX is also a powerful micro framework and using this, we can manage states, make routing, and can perform dependency injection.

2. Why Getx

GetX is focused on performance and minimum consumption of resources. One of the key performance advantages of GetX lies in its minimal overhead. By minimizing unnecessary re-renders and widget rebuilds, GetX significantly reduces the computational burden on your application, resulting in faster rendering and improved overall performance.

In addition, GetX leverages the power of efficient dependency injection. Its lightweight dependency injection mechanism enables the creation and management of dependencies without compromising performance. By efficiently managing dependencies, GetX helps eliminate unnecessary object instantiations and ensures efficient memory utilization.

3. Base Usages

3.1 Installing

Add Get to your pubspec.yaml:

dependencies:
    get: 4.6.6

and import the lib in your dart file

import 'package:get/get.dart';

3.2 State Management

GetX is easy to use, for example, you can define a variable with .obs to make it observable

var isEnabled = false.obs;

...

//change to enable
isEnabled.value = true;

and use Obx(() => ) in UI to monitor the changes

return Scaffold(
      appBar: AppBar(
        title: const Text('Test'),
        centerTitle: true,
      ),
      body: Stack(
        children: [
          Obx(
            () => controller.isEnabled.value 
                ? ListView(
                    children: <Widget>[
                     ...
                    ],
                  )
                : const SizedBox.shrink(),
          ),
        ],
      ), 
    );
  }

for the above example, if the isEnabled value changed to true, then will show the ListView data, just so easy, right?

3.3 Route Management

Navigation is very important in an App, GetX can help you easy to manage it. For example:

//navigate to a next page
Get.to(NextPage());

//navigate to next page by name
Get.tonamed('/next');

//beck to previous page or close snackbar,dialogs...
Get.back();

//to go to the next page and no option to go back to the previous page (for use in SplashPage, login Page, etc.)
Get.off(NextPage());

//to go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests)
Get.offAll(NextPage());

Ok, you can find more detailed usages in the official project website, and I will show another tip what I am using 🙂

4. Other Usages

4.1 Using in bottomNavigationBar

Most of the time, we just need to bind the dependencies in main.dart when starting the app, but if you are using the bottomNavigationBar, you also need to bind the dependence in the bottom navigation main page, for example the below structure

dashboard (bottomNavigationBar main page)
    -- home
        -- product1
            -- product1_detail
        -- product2
            -- product1_detail
    -- settings

so the bindings in dashboard should be like below

class DashboardBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<DashboardController>(
      () => DashboardController(),
    );
    Get.lazyPut<HomeController>(
      () => HomeController(),
    );
    Get.lazyPut<Product1Controller>(
      () => Product1Controller(),
    );
    Get.lazyPut<Product2Controller>(
      () => Product2Controller(),
    );
  }
}

and when you want to navigate to the product1 detail page from product1, you need to add the binding before go

return InkWell(
onTap: () {
  Get.lazyPut<Product1DetailController>(
    () => Product1DetailController(),
  );
  Get.to(() => const Product1DetailPage());
},

4.2 Using service for the common functions

GetX Service useful to let you make some common functions, you can handle the same lifecycle (onInit(), onReady(), onClose()) as controller, and you can get the service anywhere with Get.find(). For example, create a local storage service for handle the SharedPreferences functions

class LocalStorageService extends GetxService {
Future<T?> getValue<T>(
    LocalStorageKeys key, [
    T Function(Map<String, dynamic>)? fromJson,
  ]) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    switch (T) {
      case int:
        return prefs.getInt(key.name) as T?;
      case double:
        return prefs.getDouble(key.name) as T?;
      case String:
        return prefs.getString(key.name) as T?;
      case bool:
        return prefs.getBool(key.name) as T?;
      default:
        assert(fromJson != null, 'fromJson must be provided for Object values');
        if (fromJson != null) {
          final stringObject = prefs.getString(key.name);
          if (stringObject == null) return null;
          final jsonObject = jsonDecode(stringObject) as Map<String, dynamic>;
          return fromJson(jsonObject);
        }
    }
    return null;
  }

  void setValue<T>(LocalStorageKeys key, T value) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    switch (T) {
      case int:
        prefs.setInt(key.name, value as int);
        break;
      case double:
        prefs.setDouble(key.name, value as double);
        break;
      case String:
        prefs.setString(key.name, value as String);
        break;
      case bool:
        prefs.setBool(key.name, value as bool);
        break;
      default:
        assert(
          value is Map<String, dynamic>,
          'value must be int, double, String, bool or Map<String, dynamic>',
        );
        final stringObject = jsonEncode(value);
        prefs.setString(key.name, stringObject);
    }
  }
}

and init the service in main.dart

Get.put(LocalStorageService());

then you can use it anywhere (including in other services)

//get the service
var localStorage = Get.find<LocalStorageService>();

...

//save the value
localStorage.setValue<String>('currentLanguage', 'en');

//get the value
var currentLanguage =
      await localStorage.getValue<String>('currentLanguage');

4.3 Using the dialog

GetX support to use the popup dialog, you can easily use the default dialog below

Get.defaultDialog(
    title: 'Title',
    titleStyle: const TextStyle(color: Colors.red),
    middleText: 'Messages');

This is only the normal alert dialog, if you want to use a custom style, you can use the below

Get.dialog(
  Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Padding(
        padding: const EdgeInsets.symmetric(horizontal: 40),
        child: Container(
          decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.all(
              Radius.circular(20),
            ),
          ),
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Material(
              child: 

              //your layout widgets

            ),
          ),
        ),
      ),
    ],
  ),
);

as you can see, you can put any widgets in Get.dialog(), so this is not only a dialog but also a popup page 🙂

and you can create the dialog as a service, there is a complete custom dialog service below:

enum DialogType {
  info,
  success,
  error,
  warning,
}


class DialogService extends GetxService {
  showAlert(
    String title,
    String message, {
    DialogType dialogType = DialogType.info,
    Function? callback,
  }) {
    IconData iconData = Icons.info;
    Color iconColor = Colors.blueGrey;
    if (dialogType == DialogType.error) {
      iconData = Icons.error;
      iconColor = Colors.red;
    } else if (dialogType == DialogType.warning) {
      iconData = Icons.warning;
      iconColor = Colors.yellow;
    } else if (dialogType == DialogType.success) {
      iconData = Icons.done_rounded;
      iconColor = Colors.green;
    }

    Get.dialog(
      Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 40),
            child: Container(
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.all(
                  Radius.circular(20),
                ),
              ),
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Material(
                  child: Column(
                    children: [
                      const SizedBox(height: 10),
                      Icon(
                        iconData,
                        color: iconColor,
                        size: 50,
                      ),
                      const SizedBox(height: 10),
                      Text(
                        title,
                        style: const TextStyle(
                            fontSize: 24, fontWeight: FontWeight.bold),
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 20),
                      Text(
                        message,
                        style: const TextStyle(fontSize: 18),
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 50),
                      //Buttons
                      Row(
                        children: [
                          Expanded(
                            child: ElevatedButton(
                              style: ElevatedButton.styleFrom(
                                foregroundColor: const Color(0xFFFFFFFF),
                                backgroundColor: Colors.blue,
                                minimumSize: const Size(0, 45),
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(8),
                                ),
                              ),
                              onPressed: () {
                                callback != null ? callback() : null;
                              },
                              child: Text(
                                style: const TextStyle(fontSize: 18),
                                LabelKeys.ok.tr,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

you can use it anywhere

final dialog = Get.find<DialogService>();


//show the normal alert
dialog.showAlert(
  'Title',
  'This is a normal info alert'
);

//show the error alert
dialog.showAlert(
  'Error',
  'This is an error alert',
  dialogType: DialogType.error
);

5. Advanced Apis

There are also many advanced APIs that can be use, such like

// give the current args from currentScreen
Get.arguments

// give name of previous route
Get.previousRoute

// give the raw route to access for example, rawRoute.isFirst()
Get.rawRoute

// give access to Routing API from GetObserver
Get.routing

// check if snackbar is open
Get.isSnackbarOpen

// check if dialog is open
Get.isDialogOpen

// check if bottomsheet is open
Get.isBottomSheetOpen

//Check in what platform the app is running
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia

//Check the device type
GetPlatform.isMobile
GetPlatform.isDesktop
//All platforms are supported independently in web!
//You can tell if you are running inside a browser
//on Windows, iOS, OSX, Android, etc.
GetPlatform.isWeb


// Equivalent to : MediaQuery.of(context).size.height,
// but immutable.
Get.height
Get.width

// Gives the current context of the Navigator.
Get.context

// Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code.
Get.contextOverlay

...

you can find more in here

6. Conclusion

GetX is powerful, it’s saved my many times, and it’s easy to understand, it also has a highly active and helpful community, you can find the solutions in their community if there are any problems, and you also can install the vs-code extension to help to build the GetX project. Please let me know if you have another opinion of GetX 🙂

Loading

<p>The post How the GetX easy to use in Flutter first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/12/how-the-getx-easy-to-use-in-flutter/feed/ 9
Introducing MAUI: Microsoft’s Next-Gen Multi-Platform UI Framework https://www.coderblog.in/2023/04/introducing-maui-microsofts-next-gen-multi-platform-ui-framework/ https://www.coderblog.in/2023/04/introducing-maui-microsofts-next-gen-multi-platform-ui-framework/#comments Tue, 25 Apr 2023 14:40:46 +0000 https://www.coderblog.in/?p=752 Wow, have you heard of MAUI? It’s Microsoft’s latest framework for building cross-platform mobile applications! MAUI, which stands

<p>The post Introducing MAUI: Microsoft’s Next-Gen Multi-Platform UI Framework first appeared on Coder Blog.</p>

]]>
Wow, have you heard of MAUI? It’s Microsoft’s latest framework for building cross-platform mobile applications! MAUI, which stands for Multi-platform App UI, is the successor to Xamarin.Forms and will allow developers to create mobile apps that can run on Android, iOS, and Windows.

With MAUI, you can develop native apps using C# and .NET, and enjoy a unified API surface across all platforms. This means you can write code once and run it on multiple devices without having to worry about platform-specific code.

What’s even more exciting is that MAUI comes with a whole new set of controls and features, such as a new FlexLayout for building responsive layouts and a FontIcon control for displaying icons as fonts. Plus, MAUI leverages the power of .NET 6, making it easier to build performant and high-quality apps.

MAUI is an incredible tool that simplifies the process of building cross-platform mobile apps, and I can’t wait to see what developers will create with it!

Ok, now let me introduce it in detail

1. What’s MAUI?

MAUI, or Multi-platform App UI, is a new framework from Microsoft that allows developers to build native mobile apps for iOS, Android, and Windows using a single codebase. It is an evolution of Xamarin.Forms, a popular cross-platform framework that has been used to build thousands of apps for over a decade.

The advantages of using MAUI over Xamarin.Forms are numerous. First and foremost, MAUI is built on .NET 6, the latest version of Microsoft’s powerful development platform. This means that MAUI apps are faster, more efficient, and more secure than Xamarin.Forms apps. MAUI also includes a number of new features and improvements, such as improved performance, enhanced UI controls, and better support for modern app design patterns.

But perhaps the biggest advantage of using MAUI is the ability to build truly native apps that look and feel great on every platform. With MAUI, you can take advantage of platform-specific UI controls, animations, and behaviors to create apps that feel right at home on iOS, Android, and Windows. This not only makes your apps look better, but it also improves user engagement and retention.

2. MAUI Architecture

MAUI (Multi-platform App UI) architecture is designed to simplify the process of building cross-platform applications. It provides a unified model for building applications across different platforms such as Android, iOS, Windows, and macOS.

MAUI architecture is built on top of .NET MAUI, which is a framework that provides a set of libraries and tools to develop cross-platform apps. MAUI is an evolution of Xamarin Forms, which was previously used to build cross-platform applications.

The main components of the MAUI architecture are:

a. Presentation Layer: This layer is responsible for displaying the user interface of the application. It uses XAML (Extensible Application Markup Language) to define the UI and supports a wide range of controls and layouts.

b. Application Layer: This layer is responsible for managing the application lifecycle and providing access to the underlying platform-specific services such as camera, GPS, and sensors.

c. Services Layer: This layer provides access to platform-specific services and APIs such as authentication, storage, and notifications.

d. .NET MAUI Libraries: These libraries provide a set of APIs and services that can be used to develop cross-platform applications.

the MAUI architecture provides a simplified approach to building cross-platform applications by providing a unified model for building applications across different platforms. This makes it easier for developers to create high-quality applications that work seamlessly across multiple platforms.

3. Getting Started with MAUI

Getting started with MAUI can be an exciting journey for developers who want to create multi-platform applications using a single codebase. Before diving into MAUI development, there are a few prerequisites that need to be fulfilled.

Firstly, it’s essential to have a good understanding of the C# programming language as well as the .NET ecosystem. This will ensure a smoother transition into MAUI development.

Next, you’ll need to make sure that your development environment is set up correctly. The official MAUI documentation provides comprehensive instructions on how to set up your development environment for macOS, Windows, and Linux.

Once your development environment is set up, you can create your first MAUI application. MAUI comes with a project template that includes preconfigured settings, allowing you to start developing your application immediately. You can use Visual Studio or Visual Studio for Mac to create your MAUI project.

4. MAUI Controls

In terms of controls, MAUI offers a wide range of options, including layouts, views, buttons, labels, text fields, sliders, and more. These controls are designed to be highly customizable, allowing developers to tweak their appearance and behavior to match their app’s specific needs.

One of the major advantages of MAUI controls over Xamarin.Forms controls is that they are designed to be more consistent across different platforms. This means that developers can create apps that look and feel native on iOS, Android, Windows, and macOS without having to write separate code for each platform. Additionally, MAUI controls are optimized for performance, ensuring that apps are responsive and run smoothly on all devices.

In comparison, Xamarin.Forms controls can sometimes look and behave differently across platforms, which can lead to a less consistent user experience. However, Xamarin.Forms does offer a wider range of controls than MAUI, which can be beneficial for some app development scenarios.

MAUI controls provide developers with a powerful set of tools for creating high-quality, cross-platform apps with a consistent user interface. By leveraging these controls, developers can build apps that feel native on every platform while also taking advantage of the benefits of a shared codebase.

5. MAUI Platform-Specific Implementations

MAUI Platform-Specific Implementations are an essential aspect of MAUI development. The MAUI framework allows developers to create cross-platform applications that can run on different platforms such as Android, iOS, and Windows. However, these platforms have their unique features and APIs, which require platform-specific implementations to access them.

To create platform-specific implementations in MAUI, developers can use the Platform-Specific APIs. These APIs allow developers to write code that is specific to a particular platform. Developers can create platform-specific implementations using the C# language, which is the primary language for developing MAUI applications.

One of the essential components of MAUI platform-specific implementations is the DependencyService. This service allows developers to access platform-specific features by registering platform-specific implementations in a shared codebase. The DependencyService makes it possible for developers to write platform-agnostic code that can access platform-specific features.

MAUI Platform-Specific Implementations make it possible for developers to create cross-platform applications that can take advantage of the unique features and APIs of different platforms. This feature is one of the many reasons why MAUI is an excellent choice for developing cross-platform applications.

6. MAUI Development Best Practices

When developing with MAUI, there are certain best practices that can help you to create high-quality applications. These practices include:

  1. Tips and Tricks for Developing with MAUI:
  • Understand the MVVM (Model-View-ViewModel) architecture: MAUI follows the MVVM pattern, which separates the UI logic from the business logic, making your code more maintainable and testable.
  • Use XAML to define your UI: XAML is a markup language that helps you define your UI and data bindings in a clear and concise way.
  • Use Styles and Templates: Styles and Templates help you to define the look and feel of your UI consistently, reducing redundant code and improving maintainability.
  • Use ResourceDictionaries: ResourceDictionaries allow you to define resources that can be shared across your application, improving efficiency and reducing redundancy.
  • Use Data Binding: Data Binding is a powerful feature that enables you to establish a connection between your UI elements and the underlying data model. This makes your code more maintainable and reduces boilerplate code.
  • Use Commands: Commands are a way to abstract user actions into reusable objects. This helps you to write code that is more modular and reusable.
  1. Common pitfalls to avoid when developing with MAUI:
  • Avoid using platform-specific APIs directly: If you use platform-specific APIs directly, you may end up with code that is not portable across platforms. Instead, use the DependencyService to access platform-specific features.
  • Avoid tight coupling between UI and business logic: When developing your UI, avoid tightly coupling your UI code with your business logic. Instead, use the MVVM pattern to separate your UI and business logic.
  • Avoid unnecessary code duplication: Use inheritance, composition, and other design patterns to reduce code duplication and improve maintainability.
  • Avoid blocking the UI thread: Long-running operations should be performed on a separate thread to avoid blocking the UI thread, which can lead to a poor user experience.
  1. Best practices for performance optimization:
  • Use async/await: Asynchronous programming can help you to improve the responsiveness of your UI by freeing up the UI thread for other tasks.
  • Use caching: Caching can help you to improve the performance of your application by reducing the number of network requests and database queries that need to be made.
  • Optimize images: Images can take up a lot of memory and slow down your application. Use appropriate image compression techniques to optimize the size of your images.
  • Reduce unnecessary animations: Animations can be a great way to make your application more engaging, but too many animations can slow down your application. Use animations judiciously.

By following these best practices, you can create high-quality MAUI applications that are portable, maintainable, and performant.

7. MAUI Future Roadmap

MAUI, as an open-source platform, has an exciting future ahead of it. The development team has already made significant progress in enhancing the platform since its first preview was released, and they continue to work on new features and improvements.

One of the most anticipated upcoming features is support for desktop platforms, such as Windows and macOS, in addition to iOS and Android. This will allow developers to create cross-platform desktop applications using the same codebase and tools they use for mobile app development.

Another important feature that is being worked on is enhanced support for performance optimization. As mobile apps become more complex, ensuring that they run smoothly and efficiently is crucial. MAUI developers are working on new ways to improve performance, such as optimizing rendering and reducing memory usage.

In terms of future direction and plans, the MAUI team is committed to making the platform more accessible to developers. They are exploring ways to simplify the development process and make it easier for developers to create high-quality, cross-platform apps.

As MAUI gains popularity and adoption, it has the potential to significantly impact the mobile app development landscape. Developers can use MAUI to create native, cross-platform apps that are fast, reliable, and scalable. With support for desktop platforms on the horizon, MAUI could become the go-to platform for developers looking to create cross-platform desktop and mobile apps.

In summary, MAUI is a powerful and exciting new framework for building cross-platform mobile applications that builds upon the foundation of Xamarin.Forms. With its improved performance, enhanced controls, and increased platform-specific capabilities, MAUI offers a more robust and streamlined development experience for mobile app developers. By following best practices and taking advantage of platform-specific implementations, developers can create high-quality and performant MAUI applications. Looking towards the future, MAUI has an ambitious roadmap that promises to bring even more exciting features and improvements, potentially making it a game-changer in the mobile app development landscape.

Loading

<p>The post Introducing MAUI: Microsoft’s Next-Gen Multi-Platform UI Framework first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/04/introducing-maui-microsofts-next-gen-multi-platform-ui-framework/feed/ 10
Upload the image with xamarin form https://www.coderblog.in/2020/05/upload-the-image-with-xamarin-form/ https://www.coderblog.in/2020/05/upload-the-image-with-xamarin-form/#comments Fri, 22 May 2020 03:30:42 +0000 http://www.coderblog.in/?p=266 For upload an image or file to a remote server, you should convert the file to byte, you

<p>The post Upload the image with xamarin form first appeared on Coder Blog.</p>

]]>
For upload an image or file to a remote server, you should convert the file to byte, you can find how to do it in here.

We need to put the image to the MultipartFormDataContent and send to server for upload, so need to create the MultipartFormDataContent object first:

MultipartFormDataContent content = new MultipartFormDataContent();
ByteArrayContent baContent = new ByteArrayContent(logo); //the logo is the byte[] object from an image or file
StringContent id = new StringContent("id"); //this is another paramter you need to pass to server
baContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpg"); //set the media type is an image
baContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
    FileName = companyId + "_logo.jpg",   // save the file name into server side
    Name = "file",
};
content.Add(baContent, "file");
content.Add(id, companyId.ToString());

After that we need to pass the content via the server API:

var response = await client.PostAsync("http://www.yourserver.com/upload", content);

var result = response.Content.ReadAsStringAsync().Result;

if (result == null || result.ToString() != "\"OK\"")
{
    return false;
}

I just show you the keycodes, of course, you should do some error handling for this case, something like the try…catch…, and below is the full function for this upload case:

public static async Task<bool> UploadCompanyLogo(int companyId, byte[] logo)
{
    try
    {
        MultipartFormDataContent content = new MultipartFormDataContent();
        ByteArrayContent baContent = new ByteArrayContent(logo); //the logo is the byte[] object from an image or file
        StringContent id = new StringContent("id"); //this is another paramter you need to pass to server
        baContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpg"); //set the media type is an image
        baContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = companyId + "_logo.jpg",   // save the file name into server side
            Name = "file",
        };
        content.Add(baContent, "file");
        content.Add(id, companyId.ToString());


        var response = await client.PostAsync("http://www.yourserver.com/upload", content);

        var result = response.Content.ReadAsStringAsync().Result;

        if (result == null || result.ToString() != "\"OK\"")
        {
            return false;
        }
    }
    catch(Execption ex)
    {
        //do something to write the error logs....
        return false;
    }
    return true;
}

Loading

<p>The post Upload the image with xamarin form first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2020/05/upload-the-image-with-xamarin-form/feed/ 14