Bulah  Pfeffer

Bulah Pfeffer

1632541370

How to Create Modal Bottom Sheet in Flutter

Learn how to create a modal bottom sheet in Flutter with this short guide, complete with practical examples.

In this tutorial, we’ll demonstrate how to create a modal bottom sheet in Flutter with practical examples.

Here’s what we’ll cover:

  • Types of bottom sheets in Flutter
  • What is a modal bottom sheet?
  • What is a modal bottom sheet used for?
  • The showModalBottomSheet function
  • How to create a modal bottom sheet in Flutter

#fluter

How to Create Modal Bottom Sheet in Flutter
Antwan  Larson

Antwan Larson

1629821018

How to Create Login anđ Register Using Flutter Firebase Auth with Bloc

Learn How to Create Login anđ Register Using Flutter Firebase Auth with Bloc

▶SourceCode
* Github
https://github.com/PuzzleLeaf/FlutterFirebaseAuthWithBloc

▶Timestamp
00:00 - Intro
00:20 - Main
00:30 - LoginScreen
01:02 - CurvedWidget, ClipPath, CustomClipper
05:15 - LoginForm
07:05 - GradientButton
09:26 - LoginForm
10:03 - Reigister Screen
11:10 - Firebase Auth, Flutter Bloc, Equatable
11:48 - Repository 
13:08 - AuthenticationState
14:02 - AuthenticationEvent
14:27 - AuthenticationBloc
16:45 - LoginBloc Start
17:08 - LoginState
18:58 - LoginEvent
19:48 - LoginBloc
21:52 - Validators
22:11 - BlocObserver
23:37 - Authentication
24:47 - Login
30:50 - RegisterState, RegisterState, RegisterBloc
32:35 - Register

#fluter #firebase 

How to Create Login anđ Register Using Flutter Firebase Auth with Bloc

Flutter plugin for Twilio Programmable Video

twilio_programmable_video .Flutter plugin for Twilio Programmable Video, which enables you to build real-time videocall applications (WebRTC) 
This Flutter plugin is a community-maintained project for Twilio Programmable Video and not maintained by Twilio. If you have any issues, please file an issue instead of contacting support.

This package is currently work-in-progress and should not be used for production apps. We can't guarantee that the current API implementation will stay the same between versions, until we have reached v1.0.0.

Example

Check out our comprehensive example provided with this plugin.

Twilio Programmable Video Example

Join the community

If you have any question or problems, please join us on Discord

FAQ

Read the Frequently Asked Questions first before creating a new issue.

Supported platforms

  • Android
  • iOS
  • ~~Web~~ (not yet)

Getting started

Prerequisites

Before you can start using the plugin you need to make sure you have everything setup for your project.

Android

For this plugin to work for Android, you will have to tweak a few files.

Permissions

Open the AndroidManifest.xml file in your android/app/src/main directory and add the following device permissions:

...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
...

Proguard

Add the following lines to your android/app/proguard-rules.pro file.

-keep class tvi.webrtc.** { *; }
-keep class com.twilio.video.** { *; }
-keep class com.twilio.common.** { *; }
-keepattributes InnerClasses

Also do not forget to reference this proguard-rules.pro in your android/app/build.gradle file.

android {

    ...

    buildTypes {

        release {

            ...    

            minifyEnabled true
            useProguard true

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
    }
}

iOS

For this plugin to work for iOS, you will have to tweak a few files.

Permissions

Open the Info.plist file in your ios/Runner directory and add the following permissions:

...
<key>NSCameraUsageDescription</key>
<string>Your message to user when the camera is accessed for the first time</string>
<key>NSMicrophoneUsageDescription</key>
<string>Your message to user when the microphone is accessed for the first time</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
...

Setting minimal iOS target to 11

  1. In Xcode, open Runner.xcworkspace in your app's ios folder.
  2. To view your app’s settings, select the Runner project in the Xcode project navigator. Then, in the main view sidebar, select the Runner target.
  3. Select the General tab.
  4. In the Deployment Info section, set the Target to iOS 11.

Background Modes

To allow a connection to a Room to be persisted while an application is running in the background, you must select the Audio, AirPlay, and Picture in Picture background mode from the Capabilities project settings page. See Twilio Docs for more information.

Connect to a Room

Call TwilioProgrammableVideo.connect() to connect to a Room in your Flutter application. Once connected, you can send and receive audio and video streams with other Participants who are connected to the Room.

Room _room;
final Completer<Room> _completer = Completer<Room>();

void _onConnected(Room room) {
  print('Connected to ${room.name}');
  _completer.complete(_room);
}

void _onConnectFailure(RoomConnectFailureEvent event) {
  print('Failed to connect to room ${event.room.name} with exception: ${event.exception}');
  _completer.completeError(event.exception);
}
  
Future<Room> connectToRoom() async {
  var connectOptions = ConnectOptions(
    accessToken,
    roomName: roomName,                   // Optional name for the room
    region: region,                       // Optional region.
    preferredAudioCodecs: [OpusCodec()],  // Optional list of preferred AudioCodecs
    preferredVideoCodecs: [H264Codec()],  // Optional list of preferred VideoCodecs.
    audioTracks: [LocalAudioTrack(true)], // Optional list of audio tracks.
    dataTracks: [
      LocalDataTrack(
        DataTrackOptions(
          ordered: ordered,                      // Optional, Ordered transmission of messages. Default is `true`.
          maxPacketLifeTime: maxPacketLifeTime,  // Optional, Maximum retransmit time in milliseconds. Default is [DataTrackOptions.defaultMaxPacketLifeTime]
          maxRetransmits: maxRetransmits,        // Optional, Maximum number of retransmitted messages. Default is [DataTrackOptions.defaultMaxRetransmits]
          name: name                             // Optional
        ),                                // Optional
      ),
    ],                                    // Optional list of data tracks   
    videoTracks: ([LocalVideoTrack(true, CameraCapturer(CameraSource.FRONT_CAMERA))]), // Optional list of video tracks.
  );
  _room = await TwilioProgrammableVideo.connect(connectOptions);
  _room.onConnected.listen(_onConnected);
  _room.onConnectFailure.listen(_onConnectFailure);
  return _completer.future;
}

You must pass the Access Token when connecting to a Room.

Join a Room

If you'd like to join a Room you know already exists, you handle that exactly the same way as creating a room: just pass the Room name to the connect method. Once in a Room, you'll receive a RoomParticipantConnectedEvent for each Participant that successfully joins. Querying the room.remoteParticipants getter will return any existing Participants who have already joined the Room.

Room _room;
final Completer<Room> _completer = Completer<Room>();

void _onConnected(Room room) {
  print('Connected to ${room.name}');
  _completer.complete(_room);
}

void _onConnectFailure(RoomConnectFailureEvent event) {
  print('Failed to connect to room ${event.room.name} with exception: ${event.exception}');
  _completer.completeError(event.exception);
}
  
Future<Room> connectToRoom() async {
  var connectOptions = ConnectOptions(
    accessToken,
    roomName: roomName,
    region: region,                       // Optional region.
    preferAudioCodecs: [OpusCodec()],     // Optional list of preferred AudioCodecs
    preferVideoCodecs: [H264Codec()],     // Optional list of preferred VideoCodecs.
    audioTracks: [LocalAudioTrack(true)], // Optional list of audio tracks.
    dataTracks: [
      LocalDataTrack(
        DataTrackOptions(
          ordered: ordered,                      // Optional, Ordered transmission of messages. Default is `true`.
          maxPacketLifeTime: maxPacketLifeTime,  // Optional, Maximum retransmit time in milliseconds. Default is [DataTrackOptions.defaultMaxPacketLifeTime]
          maxRetransmits: maxRetransmits,        // Optional, Maximum number of retransmitted messages. Default is [DataTrackOptions.defaultMaxRetransmits]
          name: name                             // Optional
        ),                                // Optional
      ),
    ],                                    // Optional list of data tracks
    videoTracks([LocalVideoTrack(true, CameraCapturer(CameraSource.FRONT_CAMERA))]), // Optional list of video tracks. 
  );
  _room = await TwilioProgrammableVideo.connect(connectOptions);
  _room.onConnected.listen(_onConnected);
  _room.onConnectFailure.listen(_onConnectFailure);
  return _completer.future;
}

Set up local media

You can capture local media from your device's microphone or camera in the following ways:

// Create an audio track.
var localAudioTrack = LocalAudioTrack(true);

// A video track request an implementation of VideoCapturer.
var cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);

// Create a video track.
var localVideoTrack = LocalVideoTrack(true, cameraCapturer);

// Getting the local video track widget.
// This can only be called after the TwilioProgrammableVideo.connect() is called.
var widget = localVideoTrack.widget();

Connect as a publish-only Participant

It is currently not possible to connect as a publish-only participant.

Working with Remote Participants

Handle Connected Participants

When you join a Room, Participants may already be present. You can check for existing Participants when the Room.onConnected listener gets called by using the room.remoteParticipants getter.

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onConnected((Room room) {
  print('Connected to ${room.name}');
});

room.onConnectFailure((RoomConnectFailureEvent event) {
    print('Failed connecting, exception: ${event.exception.message}');
});

room.onDisconnected((RoomDisconnectEvent event) {
  print('Disconnected from ${event.room.name}');
});

room.onRecordingStarted((Room room) {
  print('Recording started in ${room.name}');
});

room.onRecordingStopped((Room room) {
  print('Recording stopped in ${room.name}');
});

// ... Assume we have received the connected callback.

// After receiving the connected callback the LocalParticipant becomes available.
var localParticipant = room.localParticipant;
print('LocalParticipant ${room.localParticipant.identity}');

// Get the first participant from the room.
var remoteParticipant = room.remoteParticipants[0];
print('RemoteParticipant ${remoteParticipant.identity} is in the room');

Handle Participant Connection Events

When Participants connect to or disconnect from a Room that you're connected to, you'll be notified via an event listener. These events help your application keep track of the participants who join or leave a Room.

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onParticipantConnected((RoomParticipantConnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has joined the room');
});

room.onParticipantDisconnected((RoomParticipantDisconnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has left the room');
});

Display a Remote Participant's Widget

To see the Video Tracks being sent by remote Participants, we need to add their widgets to the tree.

room.onParticipantConnected((RoomParticipantConnectedEvent roomEvent) {
  // We can respond when the Participant adds a VideoTrack by adding the widget to the tree.
  roomEvent.remoteParticipant.onVideoTrackSubscribed((RemoteVideoTrackSubscriptionEvent event) {
    var mirror = false;
    _widgets.add(event.remoteParticipant.widget(mirror));
  });
});

Using the DataTrack API

The DataTrack API lets you create a DataTrack channel which can be used to send low latency messages to zero or more receivers subscribed to the data.

Currently the only way you can start using a DataTrack is by specifying it in the ConnectOptions when connecting to a room

After you have connected to the Room, you have to wait until you receive the LocalDataTrackPublishedEvent before you can start sending data to the track. You can start listening for this event once you have connected to the room using the Room.onConnected listener:

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onConnected((Room room) {
  // Once connected to the room start listening for the moment the LocalDataTrack gets published to the room.
  room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished);
});

  // Once connected to the room start listening for the moment the LocalDataTrack gets published to the room.
  event.room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished);
});

void _onLocalDataTrackPublished(LocalDataTrackPublishedEvent event) {
  // This event contains a localDataTrack you can use to send data.
  event.localDataTrackPublication.localDataTrack.send('Hello world');
}

If you want to receive data from a RemoteDataTrack you have to start listening to the track once the RemoteParticipant has started publishing it and you are subscribed to it:

// Connect to a room.
var room = await TwilioProgrammableVideo.connect(connectOptions);

room.onParticipantConnected((RoomParticipantConnectedEvent event) {
  // A participant connected, now you can start listening to RemoteParticipant events
  event.remoteParticipant.onDataTrackSubscribed.listen(_onDataTrackSubscribed)
});

void _onDataTrackSubscribed(RemoteDataTrackSubscriptionEvent event) {
  final dataTrack = event.remoteDataTrackPublication.remoteDataTrack;
  dataTrack.onMessage.listen(_onMessage);
}

void _onMessage(RemoteDataTrackStringMessageEvent event) {
  print('onMessage => ${event.remoteDataTrack.sid}, ${event.message}');
}

Remember, you will not receive messages that were send before you started listening.

Participating in a Room

Display a Camera Preview

Just like Twilio we totally get that you want to look fantastic before entering a Room. Sadly that isn't yet implemented so you should go analog and use a mirror.

Disconnect from a Room

You can disconnect from a Room you're currently participating in. Other Participants will receive a RoomParticipantDisconnectedEvent.

// To disconnect from a Room, we call:
await room.disconnect();

// This results in a call to Room#onDisconnected
room.onDisconnected((RoomDisconnectEvent event) {
  print('Disconnected from ${event.room.name}');
});

Room reconnection

A Room reconnection is triggered due to a signaling or media reconnection event.

/// Exception will be either TwilioException.signalingConnectionDisconnectedException or TwilioException.mediaConnectionErrorException
room.onReconnecting((RoomReconnectingEvent event) {
  print('Reconnecting to room ${event.room.name}, exception = ${event.exception.message}');
});

room.onReconnected((Room room) {
  print('Reconnected to room ${room.name}');
});

Configuring Audio, Video Input and Output devices

Taking advantage of the ability to control input and output devices lets you build a better end user experience.

Selecting a specific Video Input

The CameraCapturer class is used to provide video frames for LocalVideoTrack from a given CameraSource.

// Share your camera.
var cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);
var localVideoTrack = LocalVideoTrack(true, cameraCapturer);

// Render camera to a widget (only after connect event).
var mirror = true;
var widget = localVideoTrack.widget(mirror);
_widgets.add(widget);

// Switch the camera source.
var cameraSource = cameraCapturer.getCameraSource();
cameraCapturer.switchCamera();
primaryVideoView.setMirror(cameraSource == CameraSource.BACK_CAMERA);

Selecting a specific Audio output

Using the TwilioProgrammableVideo class, you can specify if audio is routed through the headset or speaker.

Note:

Calling this method before being connected to a room on iOS will result in nothing. If you wish to route audio through the headset or speaker call this method in the onConnected event.

// Route audio through speaker
TwilioProgrammableVideo.setSpeakerphoneOn(true);

// Route audio through headset
TwilioProgrammableVideo.setSpeakerphoneOn(false);

Playing audio files to provide a rich user experience

For the purposes of playing audio files while using this plugin, we recommend the ocarina plugin (v0.0.5 and upwards).

This recommendation comes after surveying the available plugins for this functionality in the Flutter ecosystem for plugins that play nice with this one.

The primary problem observed with other plugins that provide this functionality is that on iOS the majority of them modify the AVAudioSession mode, putting it into a playback only mode, and as a result preventing the video call from recording audio.

The secondary problem with audio file playback in iOS is that the operating system gives priority to the VoiceProcessingIO Audio Unit, causing other audio sources to be played at a greatly diminished volume when this AudioUnit is in use. To address this issue, we provide the custom AVAudioEngineDevice which users of the plugin may enable with the example that follows. AVAudioEngineDevice was designed with ocarina in mind, providing an interface for delegating audio file playback and management from that plugin to the AVAudioEngineDevice. It was adapted from Twilio's example.

To enable usage of the AVAudioEngineDevice, and delegate audio file playback management from ocarina to it, update your AppDelegate.swifts didFinishLaunch method as follows:

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    let audioDevice = AVAudioEngineDevice()
    SwiftTwilioProgrammableVideoPlugin.audioDevice = audioDevice
    SwiftOcarinaPlugin.useDelegate(
        load: audioDevice.addMusicNode,
        dispose: audioDevice.disposeMusicNode,
        play: audioDevice.playMusic,
        pause: audioDevice.pauseMusic,
        resume: audioDevice.resumeMusic,
        stop: audioDevice.stopMusic,
        volume: audioDevice.setMusicVolume,
        seek: audioDevice.seekPosition,
        position: audioDevice.getPosition
    )

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

Once you have done this, you should be able to continue using this plugin, and ocarina as normal.

Enable debug logging

Using the TwilioProgrammableVideo class, you can enable native and dart logging of the plugin.

var nativeEnabled = true;
var dartEnabled = true;
TwilioProgrammableVideo.debug(native: nativeEnabled, dart: dartEnabled);

Access Tokens

Keep in mind, you can't generate access tokens for programmable-video using the TestCredentials, make use of the LIVE credentials.

You can easily generate an access token in the Twilio dashboard with the Testing Tools to start testing your code. But we recommend you setup a backend to generate these tokens for you and secure your Twilio credentials. Like we do in our example app.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add twilio_programmable_video

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


dependencies:
  twilio_programmable_video: ^0.9.0+2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:twilio_programmable_video/twilio_programmable_video.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:twilio_programmable_video_example/debug.dart';
import 'package:twilio_programmable_video_example/room/join_room_page.dart';
import 'package:twilio_programmable_video_example/shared/services/backend_service.dart';
import 'package:firebase_core/firebase_core.dart';

void main() {
  Debug.enabled = true;
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations(<DeviceOrientation>[
    DeviceOrientation.landscapeRight,
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(TwilioProgrammableVideoExample());
}

class TwilioProgrammableVideoExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Firebase.initializeApp(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return Provider<BackendService>(
            create: (_) => FirebaseFunctionsService.instance,
            child: MaterialApp(
              title: 'Twilio Programmable Video',
              theme: ThemeData(
                primarySwatch: Colors.blue,
                appBarTheme: AppBarTheme(
                  color: Colors.blue,
                  textTheme: TextTheme(
                    headline6: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.w500,
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
              home: JoinRoomPage(),
            ),
          );
        } else {
          return Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}

#fluter  #dart #mobile-apps

 

Flutter plugin for Twilio Programmable Video

WebEngage Flutter Plugin

WebEngage Flutter SDK

For more information checkout our website and documentation.

Installation

Add WebEngage Flutter Plugin

  • Add webengage_flutter in your pubspec.yaml file.
dependencies:
webengage_flutter: 1.0.3
  • Run flutter packages get to install the SDK

Initialization

Android

  1. Initialize WebEngage in main.dart in initState();
WebEngagePlugin _webEngagePlugin = new WebEngagePlugin();
  1. Initialize WebEngage Android SDK in your <your-project>/android/app/src/main/java/<your-package-path>/MainApplication.java class.
...
import com.webengage.sdk.android.WebEngageActivityLifeCycleCallbacks;
import io.flutter.app.FlutterApplication;

public class MainApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        super.onCreate();
         WebEngageConfig webEngageConfig = new WebEngageConfig.Builder()
                .setWebEngageKey("YOUR_LICENCSE_CODE")
                .setAutoGCMRegistrationFlag(false)
                .setLocationTrackingStrategy(LocationTrackingStrategy.ACCURACY_BEST)
                .setDebugMode(true) // only in development mode
                .build();
        WebengageInitializer.initialize(this,webEngageConfig);
        ...
    }
    ...
}

Push Notifications

  1. Add below dependencies in app-level build gradle
    implementation platform('com.google.firebase:firebase-bom:25.12.0')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-messaging:20.2.1'
    implementation 'com.google.android.gms:play-services-ads:15.0.1'
  1. Add the following to your dependencies section in project/build.gradle
        classpath 'com.google.gms:google-services:4.3.4'
  1. Firebase tokens can be passed to WebEngage using FirebaseMessagingService
import com.google.firebase.messaging.FirebaseMessagingService;
import com.webengage.sdk.android.WebEngage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onNewToken(String s) {
        super.onNewToken(s);
        WebEngage.get().setRegistrationID(s);
    }
}

It is also recommended that you pass Firebase token to WebEngage from onCreate of your Application class as shown below. This will ensure that changes in user’s Firebase token are communicated to WebEngage.

import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import com.webengage.sdk.android.WebEngage;

public class MainApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        super.onCreate();
    FirebaseMessaging.getInstance().getToken()
    .addOnCompleteListener(new OnCompleteListener<String>() {
        @Override
        public void onComplete(@NonNull Task<String> task) {
          if (!task.isSuccessful()) {
            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
            return;
          }
          // Get new FCM registration token
          String token = task.getResult();
          WebEngage.get().setRegistrationID(token);
        }
    });
     
    }
}
  1. Pass Messages to WebEngage Create a class that extends FirebaseMessagingService and pass messages to WebEngage. All incoming messages from WebEngage will contain key source with the value as webengage.
package your.application.package;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.webengage.sdk.android.WebEngage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    Map<String, String> data = remoteMessage.getData();
    if(data != null) {
      if(data.containsKey("source") && "webengage".equals(data.get("source"))) {
        WebEngage.get().receive(data);
      }
    }
  }
}

Next, register the service to the application element of your AndroidManifest.xml as follows.

<service
    android:name=".MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

iOS

  1. Add WebEngage configurations <your-project>/ios/<YourApp>/Info.plist file.
<dict>
	<key>WEGLicenseCode</key>
	<string>YOUR-WEBENGAGE-LICENSE-CODE</string>

	<key>WEGLogLevel</key>
	<string>VERBOSE</string>
    ...
</dict>
  1. Initialize WebEngage iOS SDK in <your-project>/ios/<YourApp>/AppDelegate.m file.
#import <WebEngage/WebEngage.h>
...

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary * launchOptions {
    ...
  
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

Push Notifications

Push Notification Callbacks

  1. Add Below code in AppDelegate.h file
  #import <WebEngagePlugin.h>
  
  @property (nonatomic, strong) WebEngagePlugin *bridge;
  1. Add Below code in AppDelegate.m file
    self.bridge = [WebEngagePlugin new];
    //For setting push click callback set pushNotificationDelegate after webengage SDK is initialised
    
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions notificationDelegate:self.bridge];
    [WebEngage sharedInstance].pushNotificationDelegate = self.bridge;
  1. Add below subscribeToPushCallbacks() method in main.dart and call it from initMethod()
  void subscribeToPushCallbacks() {
      //Push click stream listener
      _webEngagePlugin.pushStream.listen((event) {
        String deepLink = event.deepLink;
        Map<String, dynamic> messagePayload = event.payload;
      });

      //Push action click listener
      _webEngagePlugin.pushActionStream.listen((event) {
        print("pushActionStream:" + event.toString());
        String deepLink = event.deepLink;
        Map<String, dynamic> messagePayload = event.payload;
      });
  }
  1. Add below code in dispose() of the main.dart
  //Close the streams in dispose()
  @override
  void dispose() {
    _webEngagePlugin.pushSink.close();
    _webEngagePlugin.pushActionSink.close();
    super.dispose();
  }

Universal Link

  1. Add Below code in AppDelegate.m file
  - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
  [[[WebEngage sharedInstance] deeplinkManager] getAndTrackDeeplink:userActivity.webpageURL callbackBlock:^(id location){
    [self.bridge trackDeeplinkURLCallback:location];
  }];
  return YES;
}
  1. Add below subscribeToTrackUniversalLink() method in main.dart and call it from initMethod()
 void subscribeToTrackUniversalLink() {
    _webEngagePlugin.trackDeeplinkStream.listen((location) {
      print("trackDeeplinkStream: " + location);
    });
  }
  1. Add below code in dispose() of the main.dart
  //Close the streams in dispose()
  @override
  void dispose() {
    _webEngagePlugin.trackDeeplinkURLStreamSink.close();
    super.dispose();
  }

Track Users

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // User login
    WebEngagePlugin.userLogin('3254');

    // User logout
    WebEngagePlugin.userLogout();

    // Set user first name
    WebEngagePlugin.setUserFirstName('John');

    // Set user last name
    WebEngagePlugin.setUserLastName('Doe');

    // Set user email
    WebEngagePlugin.setUserEmail('john.doe@gmail.com');

    // Set user hashed email
    WebEngagePlugin.setUserHashedEmail('144e0424883546e07dcd727057fd3b62');

    // Set user phone number
    WebEngagePlugin.setUserPhone('+551155256325');

    // Set user hashed phone number
    WebEngagePlugin.setUserHashedPhone('e0ec043b3f9e198ec09041687e4d4e8d');

    // Set user company
    WebEngagePlugin.setUserCompany('WebEngage');

    // Set user birth-date, supported format: 'yyyy-MM-dd'
    WebEngagePlugin.setUserBirthDate('1994-05-24');

    // Set user gender, allowed values are ['male', 'female', 'other']
    WebEngagePlugin.setUserGender('male');

    // Set user channel opt-in status
    WebEngagePlugin.setUserOptIn('in_app', false);

    // Set user location
    WebEngagePlugin.setUserLocation(19.25, 72.45);

    // Set User Attribute with  String value
    WebEngagePlugin.setUserAttribute("twitterusename", "saurav12994");

    // Set User Attribute with  Boolean value
    WebEngagePlugin.setUserAttribute("Subscribed to email", true);

    // Set User Attribute with  Integer value
    WebEngagePlugin.setUserAttribute("Points earned", 2626);

    // Set User Attribute with  Double value
    WebEngagePlugin.setUserAttribute("Dollar Spent", 123.44);

    // Set User Attribute with  Map value
    var details = {'Usrname':'tom','Passiword':'pass@123'};
    WebEngagePlugin.setUserAttributes(details);

Track Events

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // Track simple event
      WebEngagePlugin.trackEvent('Added to Cart');

      // Track event with attributes
      WebEngagePlugin.trackEvent('Order Placed', {'Amount': 808.48});

In-app Notifications

Track Screens

import 'package:webengage_flutter/webengage_flutter.dart';
...
    // Track screen
    WebEngagePlugin.trackScreen('Home Page');

    // Track screen with data
    WebEngagePlugin.trackScreen('Product Page', {'Product Id': 'UHUH799'});

In-app Notification Callbacks

  1. Add Below code in AppDelegate.h file
  #import <WebEngagePlugin.h>
  
  @property (nonatomic, strong) WebEngagePlugin *bridge;
  1. Add Below code in AppDelegate.m file
    self.bridge = [WebEngagePlugin new];
    //For setting in-app click callback set notificationDelegate while initialising WebEngage SDK
    
    [[WebEngage sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions notificationDelegate:self.bridge];
  1. Add Below Method in main.dart
 void _onInAppPrepared(Map<String, dynamic> message) {
    print("This is a inapp Prepated callback from native to flutter. Payload " +
        message.toString());
  }
  void _onInAppClick(Map<String, dynamic> message,String s) {
    print("This is a inapp click callback from native to flutter. Payload " +
        message.toString());

  }

  void _onInAppShown(Map<String, dynamic> message) {
    print("This is a callback on inapp shown from native to flutter. Payload " +
        message.toString());
  }

  void _onInAppDismiss(Map<String, dynamic> message) {
    print("This is a callback on inapp dismiss from native to flutter. Payload " +
        message.toString());
  }
  1. Add Below code inside initmethod() in main.dart
_webEngagePlugin.setUpInAppCallbacks(
        _onInAppClick, _onInAppShown, _onInAppDismiss, _onInAppPrepared);

More Info

Questions?

Reach out to our Support Team for further assistance.

Plugin info

WebEngage Flutter SDK

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add webengage_flutter

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


dependencies:
  webengage_flutter: ^1.0.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:webengage_flutter/webengage_flutter.dart';

#fluter  #dart #mobile-apps

WebEngage Flutter Plugin

Lightweight Dart Web Server

Stream is a Dart web server supporting request routing, filtering, template engine, WebSocket, MVC design pattern and file-based static resources.

Stream is distributed under an Apache 2.0 License.

Installation

Add this to your pubspec.yaml (or create it):

dependencies:
  stream:

Usage

  • Introduction
  • Getting Started with Hello World

Compile RSP (Rikulo Stream Page) to dart files

There are two ways to compile RSP files into dart files: automatic building with Dart Editor or manual compiling.

RSP is a template technology allowing developers to create dynamically generated web pages based on HTML, XML or other document types (such as this and this).

Build with Dart Editor

To compile your RSP files automatically, you just need to add a build.dart file in the root directory of your project, with the following content:

import 'package:stream/rspc.dart';
void main(List<String> arguments) {
  build(arguments);
}

With this build.dart script, whenever your RSP is modified, it will be re-compiled.

Compile Manually

To compile a RSP file manually, run rspc (RSP compiler) to compile it into the dart file with command line interface as follows:

dart -c lib/rspc.dart -n dir1 dir2 file1 fire2...

A dart file is generated for each RSP file you gave. Fore more options, please run:

dart -c lib/rspc.dart -h

Notes to Contributors

Fork Stream

If you'd like to contribute back to the core, you can fork this repository and send us a pull request, when it is ready.

Please be aware that one of Stream's design goals is to keep the sphere of API as neat and consistency as possible. Strong enhancement always demands greater consensus.

If you are new to Git or GitHub, please read this guide first.

Who Uses

  • Quire - a simple, collaborative, multi-level task management tool.
  • Keikai - a sophisticated spreadsheet for big data

Use this package as an executable

1. Install it

You can install the package from the command line:

$ dart pub global activate stream

Use it

The package has the following executables:

$ rspc

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add stream

With Flutter:

 $ flutter pub add stream

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


dependencies:
  stream: ^3.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:stream/stream.dart';

#fluter  #dart #mobile-apps

Lightweight Dart Web Server

Syncfusion Flutter Treemap Library

Flutter Treemap library A Flutter Treemap library for creating interactive treemap to visualize flat and hierarchical data as rectangles that are sized and colored based on quantitative variables using squarified, slice, and dice algorithms.

Overview

Create a highly interactive and customizable Flutter Treemap that has features set like selection, legends, labels, tooltips, color mapping, and much more.

Disclaimer: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Free Syncfusion Community license. For more details, please check the LICENSE file.

Treemap features

Layouts - Use different layouts based on the algorithms such as squarified, slice, and dice to represent flat and hierarchically structured data.

  • Squarified:

Squarified layout

  • Slice:

Slice layout

  • Dice:

Dice layout

Hierarchical support - Along with the flat level, treemap supports hierarchical structure too. Each tile of the treemap is a rectangle which is filled with smaller rectangles representing sub-data.

Hierarchical support

Labels - Add any type of widgets (like text widget) to improve the readability of the individual tiles by providing brief descriptions on labels.

Selection - Allows you to select the tiles to highlight it and do any specific functionalities like showing pop-up or navigate to a different page.

Selection support

Legend - Use different legend styles to provide information on the treemap data clearly.

Legend support

Colors - Categorize the tiles on the treemap by customizing their color based on the levels. It is possible to set the tile color for a specific value or for a range of values.

Color customization

Tooltip - Display additional information about the tile using a completely customizable tooltip on the treemap.

Tooltip support

Drilldown - Drilldown the larger set of hierarchical level data for better visualization.

Sorting - Sort the tiles in a ascending or descending order.

Layout direction - Layout the tiles in all different corners of the rectangle. The possible layout directions are topLeft, topRight, bottomLeft, and bottomRight.

PointerOnLegend - Show a pointer at the top of the bar gradient legend while hovering on the tiles.

Custom background widgets - Add any type of custom widgets such as image widget as a background of the tiles to enrich the UI and easily visualize the type of data that a particular tile shows.

Treemap customization

Get the demo application

Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub.

Useful links

Take a look at the following to learn more about Syncfusion Flutter Treemap:

Installation

Install the latest version from pub.

Flutter Treemap example

Import the following package.

import 'package:syncfusion_flutter_treemap/treemap.dart';

Create Treemap

After importing the package, initialize the treemap widget as a child of any widget.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: SfTreemap(),
    ),
  );
}

Mapping the data source

To populate the data source, set its count to the dataCount property of the treemap. The data will be grouped based on the values returned from the TreemapLevel.groupMapper callback. You can have more than one TreemapLevel in the treemap levels collection to form a hierarchical treemap. The quantitative value of the underlying data has to be returned from the weightValueMapper callback. Based on this value, every tile (rectangle) will have its size.

late List<SocialMediaUsers> _source;

@override
void initState() {
  _source = <SocialMediaUsers>[
    SocialMediaUsers('India', 'Facebook', 25.4),
    SocialMediaUsers('USA', 'Instagram', 19.11),
    SocialMediaUsers('Japan', 'Facebook', 13.3),
    SocialMediaUsers('Germany', 'Instagram', 10.65),
    SocialMediaUsers('France', 'Twitter', 7.54),
    SocialMediaUsers('UK', 'Instagram', 4.93),
  ];
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfTreemap(
      dataCount: _source.length,
      weightValueMapper: (int index) {
        return _source[index].usersInMillions;
      },
      levels: [
        TreemapLevel(
          groupMapper: (int index) {
            return _source[index].country;
          },
        ),
      ],
    ),
  );
}

class SocialMediaUsers {
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  final String country;
  final String socialMedia;
  final double usersInMillions;
}

Default treemap view

Add treemap elements

Add the basic treemap elements such as tooltip, labels, and legend as shown in the below code snippet.

Tooltip - You can enable tooltip for any tile in the treemap and able to return the completely customized widget using the tooltipBuilder property.

Labels - You can add any type of custom widgets to the tiles as labels based on the index using the labelBuilder property.

Legend - You can show legend by initializing the legend property in the SfTreemap. It is possible to customize the legend item's color and text using the SfTreemap.colorMappers property.

late List<SocialMediaUsers> _source;

@override
void initState() {
  _source = <SocialMediaUsers>[
    SocialMediaUsers('India', 'Facebook', 25.4),
    SocialMediaUsers('USA', 'Instagram', 19.11),
    SocialMediaUsers('Japan', 'Facebook', 13.3),
    SocialMediaUsers('Germany', 'Instagram', 10.65),
    SocialMediaUsers('France', 'Twitter', 7.54),
    SocialMediaUsers('UK', 'Instagram', 4.93),
  ];
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfTreemap(
      dataCount: _source.length,
      weightValueMapper: (int index) {
        return _source[index].usersInMillions;
      },
      levels: [
        TreemapLevel(
          groupMapper: (int index) {
            return _source[index].country;
          },
          labelBuilder: (BuildContext context, TreemapTile tile) {
            return Padding(
              padding: const EdgeInsets.all(2.5),
              child: Text(
                '${tile.group}',
                style: TextStyle(color: Colors.black),
              ),
            );
          },
          tooltipBuilder: (BuildContext context, TreemapTile tile) {
            return Padding(
              padding: const EdgeInsets.all(10),
              child: Text(
                  'Country          : ${tile.group}\nSocial media : ${tile.weight}M',
                  style: TextStyle(color: Colors.black)),
            );
          },
        ),
      ],
    ),
  );
}

class SocialMediaUsers {
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  final String country;
  final String socialMedia;
  final double usersInMillions;
}

The following screenshot illustrates the result of the above code sample.

Treemap getting started

Support and feedback

About Syncfusion

Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies.

Today we provide 1,000+ controls and frameworks for web (ASP.NET Core, ASP.NET MVC, ASP.NET WebForms, JavaScript, Angular, React, Vue, and Blazor), mobile (Xamarin, Flutter, UWP, and JavaScript), and desktop development (WinForms, WPF, and UWP). We provide ready-to-deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add syncfusion_flutter_treemap

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


dependencies:
  syncfusion_flutter_treemap: ^19.2.55-beta

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:syncfusion_flutter_treemap/treemap.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_treemap/treemap.dart';

void main() {
  return runApp(TreemapApp());
}

/// This widget will be the root of application.
class TreemapApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Treemap Demo',
      home: MyHomePage(),
    );
  }
}

/// This widget is the home page of the application.
class MyHomePage extends StatefulWidget {
  /// Initialize the instance of the [MyHomePage] class.
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<SocialMediaUsers> _source;

  @override
  void initState() {
    _source = const <SocialMediaUsers>[
      SocialMediaUsers('India', 'Facebook', 25.4),
      SocialMediaUsers('USA', 'Instagram', 19.11),
      SocialMediaUsers('Japan', 'Facebook', 13.3),
      SocialMediaUsers('Germany', 'Instagram', 10.65),
      SocialMediaUsers('France', 'Twitter', 7.54),
      SocialMediaUsers('UK', 'Instagram', 4.93),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Treemap demo')),
      body: SfTreemap(
        dataCount: _source.length,
        weightValueMapper: (int index) {
          return _source[index].usersInMillions;
        },
        levels: <TreemapLevel>[
          TreemapLevel(
            groupMapper: (int index) {
              return _source[index].country;
            },
            labelBuilder: (BuildContext context, TreemapTile tile) {
              return Padding(
                padding: const EdgeInsets.all(2.5),
                child: Text(
                  tile.group,
                  style: const TextStyle(color: Colors.black),
                ),
              );
            },
            tooltipBuilder: (BuildContext context, TreemapTile tile) {
              return Padding(
                padding: const EdgeInsets.all(10),
                child: Text(
                    '''Country          : ${tile.group}\nSocial media : ${tile.weight}M''',
                    style: const TextStyle(color: Colors.black)),
              );
            },
          ),
        ],
      ),
    );
  }
}

/// Represents the class for social media users.
class SocialMediaUsers {
  /// Constructor of [SocialMediaUsers].
  const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions);

  /// Specifies the country.
  final String country;

  /// Specifies the type of social media.
  final String socialMedia;

  /// Specifies the users count.
  final double usersInMillions;
}

#fluter  #dart #mobile-apps

 Syncfusion Flutter Treemap Library

CloudKit Support for Flutter Via CloudKit Web Services

cloudkit_flutter .CloudKit support for Flutter via CloudKit Web Services.

Support

Currently, this library only supports Android (and iOS, although its usefulness there is fairly limited). The lack of Flutter Web support is due to one of the dependencies, webview_flutter, not supporting the Flutter Web platform 🙄.

Setup

Within your app, there are two stages involved in setting up this library. First, you must initialize the API manager with your CloudKit container, environment, and API token. Second, you must create your model classes based on the record types in CloudKit.

API Initialization

Before calls to the CloudKit API can be made, three values must be provided to the CKAPIManager:

  • CloudKit Container: The container ID used by CloudKit, which is typically iCloud. + your bundle ID.
  • CloudKit API Token: A token which must be created via the CloudKit dashboard. Importantly, you must select the last option ('cloudkit-' + container id + '://') within 'URL Redirect' for the 'Sign in Callback'. The custom URL can be any short string, such as 'redirect'.
  • CloudKit Environment: Changes whether the production or development environment is used. Corresponding values are provided as constants in the CKEnvironment class.

To initialize the manager, these three values must be passed into CKAPIManager.initManager(String container, String apiToken, CKEnvironment environment) async. This call should preferably be done in conjunction with the reflection setup, as described below.

Model Classes - Annotation

In this library, model classes must be annotated and then scanned so that reflection can be used to seamlessly convert JSON CloudKit records to a local Dart object.

There are three main types of annotations used in model files:

  • @CKRecordTypeAnnotation: to denote the name of the record type on CloudKit, and placed before the class declaration
  • @CKRecordNameAnnotation: to label the field within the local class where the CloudKit record name (a UUID) is stored
  • @CKFieldAnnotation: to associate fields in the local Dart object with record fields in CloudKit

Additionally, for the class to be scanned via reflection, you must tag the class with @reflector before the class declaration.

Below is an example of these annotations being used in a Dart file:

import 'package:cloudkit_flutter/cloudkit_flutter_model.dart';

@reflector
@CKRecordTypeAnnotation("Schedule")  // The name of the CloudKit record type is included in the annotation
class Schedule
{
  @CKRecordNameAnnotation() // No CloudKit record field name is needed as the field is always 'recordName'
  String? uuid;
  
  @CKFieldAnnotation("scheduleCode") // The name of the CloudKit record field is included in the annotation
  String? code;
  
  @CKFieldAnnotation("periodTimes")
  List<String>? blockTimes;
  
  @CKFieldAnnotation("periodNumbers")
  List<int>? blockNumbers;
}

Model Classes - Supported Field Types

Currently, most of the field types supported in CloudKit can be used in local model classes.

Many are fairly basic:

  • String
  • int
  • double
  • DateTime
  • List<String>
  • List<int>

There are a couple that require some explanation:

  • CKReference / List<CKReference>: The reference field type in CloudKit is used to create relations between two record types. The CKReference class has been created to represent this relation. To fetch the object associated with the reference, simply call the fetchFromCloud<T>() function, providing the corresponding local type (in place of T) when doing so.
  • CKAsset: The asset field type in CloudKit allows for the storage of literal bytes of data as a discrete asset. One common use for this type is to store an image. The CKAsset class has been created to represent this type, and it likewise has a fetchAsset() function to retrieve and cache the stored bytes. It also includes a getAsImage() function to convert the cached bytes to an image, if possible.
  • Subclasses of CKCustomFieldType: See below.

*More base field types will be added in later versions

Model Classes - Custom Field Types

Sometimes, a field within a CloudKit database only stores a raw value, to be later converted into an enum or more fully defined class when it reaches an app. To allow for custom classes to be used as types within model classes, the CKCustomFieldType class has been created.

There are several requirements for a subclass of CKCustomFieldType:

  • The class itself must provide a raw value type within the class declaration
  • There must be a default constructor which calls super.fromRecordField(T rawValue)
  • There must be a fromRecordField(T rawValue) constructor
  • The class must be tagged with @reflector, similar to the model classes

Below is a basic example of a custom field type class, Gender, which has int as its raw value type:

import 'package:cloudkit_flutter/cloudkit_flutter_model.dart';

@reflector
class Gender extends CKCustomFieldType<int>
{
  // Static instances of Gender with a raw value and name
  static final female = Gender.withName(0, "Female");
  static final male = Gender.withName(1, "Male");
  static final other = Gender.withName(2, "Other");
  static final unknown = Gender.withName(3, "Unknown");
  static final genders = [female, male, other, unknown];
  
  String name;
  
  // Required constructors
  Gender() : name = unknown.name, super.fromRecordField(unknown.rawValue);
  Gender.fromRecordField(int raw) : name = genders[raw].name, super.fromRecordField(raw);
  
  // Used to create static instances above
  Gender.withName(int raw, String name) : name = name, super.fromRecordField(raw);
  
  // The default toString() for CKCustomFieldType outputs the rawValue, but here it makes more sense to output the name
  @override
  String toString() => name;
}

Model Classes - Reflection Setup

Whenever you make changes to your model classes or CKCustomFieldType subclasses, you must regenerate object code to allow for reflection to be used within the library. First, ensure that the build_runner package is installed in your app's pubspec, as it is required to run the following command. Next, generate the object code by running flutter pub run build_runner build lib from the root folder of your Flutter project.

After the code has been generated, call initializeReflectable() (found within generated *.reflectable.dart files) at the start of your app before any other library calls are made. Finally, you must indicate to the CKRecordParser class which model classes should be scanned. To do this, call the CKRecordParser.createRecordStructures(List<Type>) function, listing the direct names of the local model classes within the list. To scan the Schedule class for example, we would call CKRecordParser.createRecordStructures([Schedule]). This call should preferably be done in conjunction with the API Initialization, as described above.

Usage

The main way to access the CloudKit API is through CKOperation, which is run though the execute() function. There are multiple kinds of operations, which are described below.

Operations

On creation, all operations require a string argument for the database (public, shared, private) to be used for the request. Optionally, a specific instance of a CKAPIManager can be passed in, although the shared instance is used by default. Additionally, a BuildContext can be optionally passed into the operation, in the off-chance that an iCloud sign-in view is necessary.

CKCurrentUserOperation

This operation fetches the CloudKit ID of the current user. It is also the simplest way to test if the user is signed in to iCloud, which is necessary to access the private database. Hence, the operation can be called at app launch or via a button to initiate the iCloud sign-in prompt.

Besides the default arguments for an operation as described above, this operation does not require any additional arguments.

Returned from the execute() call is the CloudKit ID of the signed-in user as a string.

CKRecordQueryOperation

This operation is the main method to retrieve records from CloudKit.

When creating the operation, you must pass in a local type for the operation to receive. For example: CKRecordQueryOperation<Schedule>(CKDatabase.PUBLIC_DATABASE) would fetch all Schedule records from the public database. Optionally, you can pass in a specific CKZone (zoneID), a List<CKFilter> (filters), or a List<CKSortDescriptor (sortDescriptors) to organize the results. You can also pass in a bool (preloadAssets) to indicate whether any CKAsset fields in fetched records should be preloaded.

Returned from the execute() call is an array of local objects with the type provided to the operation.

*More operations will be added in later versions

Request Models

In addition to the multiple kinds of operations, CloudKit provides several request parameters within its API, represented in this library by the classes below.

CKFilter

Filters are created through four main values: the name of the CloudKit record field to compare (fieldName), the CKFieldType of that record field (fieldType), the value to be compared against (fieldValue), and the CKComparator object for the desired comparison.

CKSortDescriptor

Sort descriptors are created through two main values: the name of the CloudKit record field to sort by (fieldName) and a boolean to indicate the direction (ascending).

CKZone

Zone objects are currently only containers for a zone ID string (zoneName), and can be used to specify a specific CloudKit zone for an operation. A zone object with an empty zone name will be set to the default zone.

CKQuery

Query objects are containers to store the CloudKit record type (recordType), a List<CKFilter> (filterBy), and a List<CKSortDescriptor> (sortBy).

CKRecordQueryRequest

Record query request objects represent the information needed to perform a CKRecordQueryOperation, including a CKZone (zoneID), a result limit (resultsLimit), and a CKQuery object (query).

Import points

To reduce the amount of included classes, you can choose to import a single section of the library, as described below.

cloudkit_flutter.dart

Includes all exposed classes.

cloudkit_flutter_init.dart

Includes classes necessary to initialize the API manager (CKAPIManager) and record parser (CKRecordParser).

cloudkit_flutter_model.dart

Includes classes necessary to annotate model files (CKRecordTypeAnnotation, CKRecordNameAnnotation, CKFieldAnnotation), use special field types (CKReference, CKAsset), and create custom field types (CKCustomFieldType).

cloudkit_flutter_api.dart

Includes classes necessary to call the CloudKit API (CKOperation + subclasses, CKZone, CKFilter, CKSortDescriptor).

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add cloudkit_flutter

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


dependencies:
  cloudkit_flutter: ^0.1.4

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:cloudkit_flutter/cloudkit_flutter.dart';

example/lib/main.dart

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

import 'package:cloudkit_flutter/cloudkit_flutter_init.dart';
import 'package:cloudkit_flutter/cloudkit_flutter_api.dart';

import 'model/schedule.dart';
import 'model/week_schedule.dart';
import 'model/user_schedule.dart';

import 'main.reflectable.dart'; // Import generated code.
// Run `flutter pub run build_runner build example` from the root directory to generate example.reflectable.dart code

void main() async
{
  await initializeCloudKit();
  runApp(CKTestApp());
}

// To run this example code, you must have a CloudKit container with the following structure (as can be inferred from model/user_schedule.dart):
// UserSchedule: {
//   periodNames: List<String>
//   profileImage: CKAsset
//   genderRaw: int
// }
//
// Once the container is created, enter the CloudKit container and API token (set up via the CloudKit dashboard & with the options specified in README.md) below:

Future<void> initializeCloudKit() async
{
  const String ckContainer = ""; // YOUR CloudKit CONTAINER NAME HERE
  const String ckAPIToken = ""; // YOUR CloudKit API TOKEN HERE
  const CKEnvironment ckEnvironment = CKEnvironment.DEVELOPMENT_ENVIRONMENT;

  initializeReflectable();

  CKRecordParser.createRecordStructures([
    Schedule,
    WeekSchedule,
    UserSchedule
  ]);

  await CKAPIManager.initManager(ckContainer, ckAPIToken, ckEnvironment);
}

class CKTestApp extends StatelessWidget
{
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: 'iCloud Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CKTestPage(title: "iCloud Test"),
    );
  }
}

class CKTestPage extends StatefulWidget
{
  CKTestPage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _CKTestPageState createState() => _CKTestPageState();
}

class _CKTestPageState extends State<CKTestPage>
{
  CKSignInState isSignedIn = CKSignInState.NOT_SIGNED_IN;
  String currentUserOutput = "Get current user ID (and check if signed in)";
  String userScheduleOutput = "Fetch user schedule";

  void getCurrentUserCallback(CKSignInState isSignedIn, String currentUserOutput)
  {
    setState(() {
      this.isSignedIn = isSignedIn;
      this.currentUserOutput = currentUserOutput;
    });
  }

  void getUserScheduleCallback(String schedulesOutput)
  {
    setState(() {
      this.userScheduleOutput = schedulesOutput;
    });
  }

  @override
  Widget build(BuildContext context)
  {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          children: [
            Text(currentUserOutput),
            CKSignInButton(isSignedIn: isSignedIn, callback: getCurrentUserCallback),
            Padding(padding: EdgeInsets.all(8.0)),
            Text(userScheduleOutput),
            FetchUserScheduleTestButton(isSignedIn: isSignedIn, callback: getUserScheduleCallback),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

class CKSignInButton extends StatefulWidget
{
  final Function(CKSignInState, String) callback;
  final CKSignInState isSignedIn;

  CKSignInButton({Key? key, required this.isSignedIn, required this.callback}) : super(key: key);

  @override
  State<StatefulWidget> createState() => CKSignInButtonState();
}

enum CKSignInState
{
  NOT_SIGNED_IN,
  SIGNING_IN,
  RE_SIGNING_IN,
  IS_SIGNED_IN
}

class CKSignInButtonState extends State<CKSignInButton>
{
  IconData getIconForCurrentState()
  {
    switch (widget.isSignedIn)
    {
      case CKSignInState.NOT_SIGNED_IN:
        return Icons.check_box_outline_blank;
      case CKSignInState.SIGNING_IN:
        return Icons.indeterminate_check_box_outlined;
      case CKSignInState.RE_SIGNING_IN:
        return Icons.indeterminate_check_box;
      case CKSignInState.IS_SIGNED_IN:
        return Icons.check_box;
    }
  }

  @override
  Widget build(BuildContext context)
  {
    return ElevatedButton(
        onPressed: () async {
          if (widget.isSignedIn == CKSignInState.IS_SIGNED_IN)
          {
            widget.callback(CKSignInState.RE_SIGNING_IN, "Re-signing in...");
          }
          else
          {
            widget.callback(CKSignInState.SIGNING_IN, "Signing in...");
          }

          var getCurrentUserOperation = CKCurrentUserOperation(CKDatabase.PUBLIC_DATABASE, context: context);
          var operationCallback = await getCurrentUserOperation.execute();

          switch (operationCallback.state)
          {
            case CKOperationState.success:
              var currentUserID = operationCallback.response as String;
              widget.callback(CKSignInState.IS_SIGNED_IN, currentUserID);
              break;

            case CKOperationState.authFailure:
              widget.callback(CKSignInState.NOT_SIGNED_IN, "Authentication failure");
              break;

            case CKOperationState.unknownError:
              widget.callback(CKSignInState.NOT_SIGNED_IN, "Unknown error");
              break;
          }
        },
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("Sign In with iCloud"),
            Padding(padding: EdgeInsets.all(4.0)),
            Icon(getIconForCurrentState())
          ],
        )
    );
  }
}

class FetchUserScheduleTestButton extends StatefulWidget
{
  final Function(String) callback;
  final CKSignInState isSignedIn;

  FetchUserScheduleTestButton({Key? key, required this.isSignedIn, required this.callback}) : super(key: key);

  @override
  State<StatefulWidget> createState() => FetchUserScheduleTestButtonState();
}

class FetchUserScheduleTestButtonState extends State<FetchUserScheduleTestButton>
{
  @override
  Widget build(BuildContext context)
  {
    return ElevatedButton(
        onPressed: () async {
          if (widget.isSignedIn != CKSignInState.IS_SIGNED_IN)
          {
            widget.callback("Catch: Not signed in");
            return;
          }

          var queryPeopleOperation = CKRecordQueryOperation<UserSchedule>(CKDatabase.PRIVATE_DATABASE, preloadAssets: true, context: context);
          CKOperationCallback queryCallback = await queryPeopleOperation.execute();

          List<UserSchedule> userSchedules = [];
          if (queryCallback.state == CKOperationState.success) userSchedules = queryCallback.response;

          switch (queryCallback.state)
          {
            case CKOperationState.success:
              if (userSchedules.length > 0)
              {
                testUserSchedule(userSchedules[0]);
                widget.callback("Success");
              }
              else
              {
                widget.callback("No UserSchedule records");
              }
              break;

            case CKOperationState.authFailure:
              widget.callback("Authentication failure");
              break;

            case CKOperationState.unknownError:
              widget.callback("Unknown error");
              break;
          }
        },
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("Fetch UserSchedules"),
          ],
        )
    );
  }
}

void testUserSchedule(UserSchedule userSchedule) async
{
  log(userSchedule.toString());

  // These are the class names for each period in userSchedule, automatically converted from CloudKit to the local object
  var periodNames = userSchedule.periodNames ?? [];
  log(periodNames.toString());

  // This is the data for a profile image, which can be casted (via .getAsImage()) due to `preloadAssets: true` when the operation was called
  var _ = (userSchedule.profileImage?.getAsImage() ?? AssetImage("assets/generic-user.png")) as ImageProvider;
  // If `preloadAssets: false`, the asset would have to be downloaded directly:
  await userSchedule.profileImage?.fetchAsset();
  log(userSchedule.profileImage?.size.toString() ?? 0.toString());

  // This is a custom `Gender` object, converted from a raw int form in CloudKit
  var gender = userSchedule.gender ?? Gender.unknown;
  log(gender.toString());
}

#fluter  #dart #mobile-apps

CloudKit Support for Flutter Via CloudKit Web Services

PostgreSQL Database Driver for Dart

PostgreSQL database driver for Dart A fork of Greg's PostgreSQL driver.

Differences

Array type with single dimension supported

The @@ operators supported.

DefaultTypeConverter.encodeValueDefault() assume all unknown types as JSON.

Pool.connect() throws an exception immediately if failed to connect to database.

More options to control the pool, such as limitTimeout and limitConnections

Basic usage

Obtaining a connection

var uri = 'postgres://username:password@localhost:5432/database';
connect(uri).then((conn) {
	// ...
});

SSL connections

Set the sslmode to require by appending this to the connection uri. This driver only supports sslmode=require, if sslmode is ommitted the driver will always connect without using SSL.

var uri = 'postgres://username:password@localhost:5432/database?sslmode=require';
connect(uri).then((conn) {
	// ...
});

Querying

conn.query('select color from crayons').toList().then((rows) {
	for (var row in rows) {
		print(row.color); // Refer to columns by name,
		print(row[0]);    // Or by column index.
	}
});

Executing

conn.execute("update crayons set color = 'pink'").then((rowsAffected) {
	print(rowsAffected);
});

Query Parameters

Query parameters can be provided using a map. Strings will be escaped to prevent SQL injection vulnerabilities.

conn.query('select color from crayons where id = @id', {'id': 5})
  .toList()
	.then((result) { print(result); });

conn.execute('insert into crayons values (@id, @color)',
             {'id': 1, 'color': 'pink'})
	.then((_) { print('done.'); });

Closing the connection

You must remember to call Connection.close() when you're done. This won't be done automatically for you.

Conversion of Postgresql datatypes.

Below is the mapping from Postgresql types to Dart types. All types which do not have an explicit mapping will be returned as a String in Postgresql's standard text format. This means that it is still possible to handle all types, as you can parse the string yourself.

     Postgresql type                 Dart type
	boolean                         bool
	int2, int4, int8                int
	float4, float8                  double
	numeric                         String
	timestamp, timestamptz, date    Datetime
	json, jsonb                     Map/List
	All other types                 String

Mapping the results of a query to an object

class Crayon {
	String color;
	int length;
}

conn.query('select color, length from crayons')
	.map((row) => new Crayon()
	                     ..color = row.color
	                     ..length = row.length)
	.toList()
	.then((List<Crayon> crayons) {
		for (var c in crayons) {
			print(c is Crayon);
			print(c.color);
			print(c.length);
		}
	});

Or for an immutable object:

class ImmutableCrayon {
	ImmutableCrayon(this.color, this.length);
	final String color;
	final int length;
}

conn.query('select color, length from crayons')
  .map((row) => new ImmutableCrayon(row.color, row.length))
     .toList()
	.then((List<ImmutableCrayon> crayons) {
		for (var c in crayons) {
			print(c is ImmutableCrayon);
			print(c.color);
			print(c.length);
		}
	});

Query queueing

Queries are queued and executed in the order in which they were queued.

So if you're not concerned about handling errors, you can write code like this:

conn.execute("create table crayons (color text, length int)");
conn.execute("insert into crayons values ('pink', 5)");
conn.query("select color from crayons").single.then((crayon) {
	print(crayon.color); // prints 'pink'
});

Query streaming

Connection.query() returns a Stream of results. You can use each row as soon as it is received, or you can wait till they all arrive by calling Stream.toList().

Connection pooling

In server applications, a connection pool can be used to avoid the overhead of obtaining a connection for each request.

import 'package:postgresql2/pool.dart';

main() {
  var uri = 'postgres://username:password@localhost:5432/database';
  var pool = new Pool(uri, minConnections: 2, maxConnections: 5);
  pool.messages.listen(print);
  pool.start().then((_) {
    print('Min connections established.');
    pool.connect().then((conn) { // Obtain connection from pool
      conn.query("select 'oi';")
        .toList()
        .then(print)
        .then((_) => conn.close()) // Return connection to pool
        .catchError((err) => print('Query error: $err'));
    });
  });
}

Example program

Add postgresql to your pubspec.yaml file, and run pub install.

name: postgresql_example
dependencies:
  postgresql2: any
import 'package:postgresql2/postgresql.dart';

void main() {
  var uri = 'postgres://testdb:password@localhost:5432/testdb';
  var sql = "select 'oi'"; 
  connect(uri).then((conn) {
    conn.query(sql).toList()
    	.then((result) {
    		print('result: $result');
    	})
    	.whenComplete(() {
    		conn.close();
    	});
  });
}

Testing

To run the unit tests you will need to create a database, and edit 'test/config.yaml' accordingly.

Creating a database for testing

Change to the postgres user and run the administration commands.

sudo su postgres
createuser --pwprompt testdb
  Enter password for new role: password
  Enter it again: password
  Shall the new role be a superuser? (y/n) n
  Shall the new role be allowed to create databases? (y/n) n
  Shall the new role be allowed to create more new roles? (y/n) n
createdb --owner testdb testdb
exit

Check that it worked by logging in.

psql -h localhost -U testdb -W

Enter "\q" to quit from the psql console.

Notes

Substitution of Array types

Like others, you can specify the array type in a substitution. For example,

insert into foo values(@name, @tags:array)

However, if the list can be empty, you have to specify the type explicitly since PostgreSQL cannot determine type of empty array. For example,

insert into foo values(@name, @tags:text_array)

It is equivalent to the following:

insert into foo values(@name, @tags:array::text[])

License

BSD

Utilities

  • Transaction management: https://github.com/rikulo/access
  • Entity management: https://github.com/rikulo/entity

Links

  • https://www.postgresql.org/docs/9.2/static/index.html
  • https://www.dartlang.org/

Who Uses

  • Quire - a simple, collaborative, multi-level task management tool.
  • Keikai - a sophisticated spreadsheet for big data

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add postgresql2

With Flutter:

 $ flutter pub add postgresql2

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


dependencies:
  postgresql2: ^1.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:postgresql2/constants.dart';
import 'package:postgresql2/pool.dart';
import 'package:postgresql2/postgresql.dart';

#fluter  #dart #mobile-apps

PostgreSQL Database Driver for Dart

A Flutter Plugin for using the Firebase Auth UI with Dart in Flutter

flutter_auth_ui .A Flutter plugin for using the Firebase Auth UI with Dart in Flutter apps. (not official plugin.)

Status

  • Android
    • support
  • iOS
    • support
  • web
    • support

Link

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_auth_ui

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


dependencies:
  flutter_auth_ui: ^2.3.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_auth_ui/flutter_auth_ui.dart';

example/lib/main.dart

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: [
              ElevatedButton(
                child: const Text("start ui"),
                onPressed: () async {
                  final providers = [
                    AuthUiProvider.anonymous,
                    AuthUiProvider.email,
                    AuthUiProvider.phone,
                    AuthUiProvider.apple,
                    AuthUiProvider.github,
                    AuthUiProvider.google,
                    AuthUiProvider.microsoft,
                    AuthUiProvider.yahoo,
                  ];

                  final result = await FlutterAuthUi.startUi(
                    items: providers,
                    tosAndPrivacyPolicy: TosAndPrivacyPolicy(
                      tosUrl: "https://www.google.com",
                      privacyPolicyUrl: "https://www.google.com",
                    ),
                    androidOption: AndroidOption(
                      enableSmartLock: false, // default true
                      showLogo: true, // default false
                      overrideTheme: true, // default false
                    ),
                    emailAuthOption: EmailAuthOption(
                      requireDisplayName: true, // default true
                      enableMailLink: false, // default false
                      handleURL: '',
                      androidPackageName: '',
                      androidMinimumVersion: '',
                    ),
                  );
                  print(result);
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

#fluter  #dart #mobile-apps

A Flutter Plugin for using the Firebase Auth UI with Dart in Flutter

Flutter Plugin to Implement in-app Subscriptions and Purchases

Qonversion is the data platform to power in-app subscription revenue growth.

  • fast in-app subscriptions implementation
  • back-end infrastructure to validate user receipts
  • manage cross-platform user access to paid content on your app
  • comprehensive subscription analytics
  • out-of-the-box integrations with the leading marketing, attribution, and product analytics platforms
  • push notifications and in-app messaging to win back lapsed subscribers
  • A/B Testing for in-app purchases

pubMIT License

How It Works: Product Center

  1. Application calls the purchase method of Qonversion SDK.
  2. Qonversion SDK communicates with StoreKit or Google Billing Client to make a purchase.
  3. If a purchase is successful, the SDK sends a request to Qonversion API for server-to-server purchase validation. Qonversion server receives accurate information on the in-app purchase status and user entitlements.
  4. SDK returns control to the application with a processing state.

Analytics

Monitor your in-app revenue metrics. Understand your customers and make better decisions with precise subscription revenue data.

Integrations

Send subscription data to your favorite platforms. Share your mobile and web subscription data using our powerful integrations.

Personalized push notifications & in-app messaging

Qonversion allows sending automated, personalized push notifications and in-app messages initiated by in-app purchase events. This feature is designed to increase your app's revenue and retention, provide cancellation insights, reduce subscriber churn, and improve your subscribers' user experience.

See more in the documentation

A/B Testing for in-app purchases

Boost conversion rates with paywalls and in-app purchases A/B testing. Find the best pricing and paywall variations. Be flexible to prove hypotheses without app releases.

Why Qonversion?

  • No headaches with Apple's StoreKit & Google Billing. Qonversion provides simple methods to handle Apple StoreKit & Google Billing purchase flow.
  • Receipt validation. Qonversion validates user receipts with Apple and Google to provide 100% accurate purchase information and subscription statuses. It also prevents unauthorized access to the premium features of your app.
  • Track and increase your revenue. Qonversion provides detailed real-time revenue analytics including cohort analysis, trial conversion rates, country segmentation, and much more.
  • Integrations with the leading mobile platforms. Qonversion allows sending data to platforms like AppsFlyer, Adjust, Branch, Tenjin, Facebook Ads, Amplitude, Mixpanel, and many others.
  • Change promoted in-app products. Change promoted in-app products anytime without app releases.
  • Win back lapsed subscribers. Qonversion allows sending highly targeted push notifications triggered by server-side subscription events. You can send special offers to users who just canceled a free trial or a subscription. Plus you can deliver in-app messages with a beautiful native design that you create in Qonversion.
  • A/B test and identify winning in-app purchases, subscriptions or paywals.
  • Cross-device and cross-platform access management. If you provide user authorization in your app, you can easily set Qonversion to provide premium access to authorized users across devices and operating systems.
  • SDK caches the data. Qonversion SDK caches purchase data including in-app products and permissions, so the user experience is not affected even with the slow or interrupting network connection.
  • Webhooks. You can easily send all of the data to your server with Qonversion webhooks.
  • Customer support. You can always reach out to our customer support and get the help required.

Convinced? Let's go!

Getting Started

  1. Create a project and register your app
  2. Configure entitlements
  3. Install the SDK
  4. Use all SDK features in a few lines

Documentation

Check the documentation to learn details on implementing and using Qonversion SDKs.

Help us improve the documentation

Whether you’re a core user or trying it out for the first time, you can make a valuable contribution to Qonversion by improving the documentation. Help us by:

  • sending us feedback about something you thought was confusing or simply missing
  • sending us a pull request via GitHub
  • suggesting better wording or ways of explaining certain topics in the Qonversion documentation. Use SUGGEST EDITS button in the top right corner.

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/SuperFeature)
  3. Commit your Changes. Use small commits with separate logic. (git commit -m 'Add some super feature')
  4. Push to the Branch (git push origin feature/SuperFeature)
  5. Open a Pull Request

Have a question?

Contact us via issues on GitHub or ask a question on the site.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add qonversion_flutter

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


dependencies:
  qonversion_flutter: ^4.1.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:qonversion_flutter/qonversion_flutter.dart';

example/lib/main.dart

import 'package:flutter/material.dart';

import 'home.dart';
import 'params_view.dart';
import 'products_view.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (_) => HomeView(),
        'products': (_) => ProductsView(),
        'params': (_) => ParamsView()
      },
    );
  }
}

#fluter  #dart #mobile-apps

Flutter Plugin to Implement in-app Subscriptions and Purchases

Creates A Tab Style UI with Navigation Capabilities

built_tab_navigator .Creates a Tab Style UI with Navigation capabilities.

This Plugin is intended to be used in conjuntion with built_value & built_collection since some of the API properties expect EnumClass objects.

💻 Installation

In the dependencies: section of your pubspec.yaml, add the following line:

dependencies:
  built_tab_navigator: <latest version>

❔ Usage

Import this class

import 'package:built_tab_navigator/built_tab_navigator.dart';

Default Tav

Network

BuiltTabNavigator(
        tabs: {
          ExampleTabs.example1: TabRoutesDefinition(
            initialRoute: Example1Routes.root,
            routes: example1Routes,
            tabTitle: "Example 1",
            tabIcon: Icons.ac_unit,
          ),
          ExampleTabs.example2: TabRoutesDefinition(
            initialRoute: Example2Routes.root,
            routes: example2Routes,
            tabTitle: "Example 2",
            tabIcon: Icons.accessibility_new,
          ),
          ExampleTabs.example3: TabRoutesDefinition(
            initialRoute: Example2Routes.root,
            routes: example3Routes,
            tabTitle: "Example 3",
            tabIcon: Icons.adb,
          ),
        },
      )

Try Custom Builders

Network

BuiltTabNavigator(
    iconBuilder: (context, tab, definition, selected) {
        return Container(
        padding: EdgeInsets.all(4),
        decoration: BoxDecoration(
            color: Colors.purple,
            borderRadius: BorderRadius.circular(20),
        ),
        child: Icon(definition.tabIcon, color: selected? Colors.white : Colors.black,),
        );
    },
    titleBuilder: (context, tab, definition, selected) {
        return Text(definition.tabTitle, style: TextStyle(fontSize: 20, color: selected? Colors.purple : Colors.grey),);
    },
    tabs: {
        ExampleTabs.example1: TabRoutesDefinition(
        initialRoute: Example1Routes.root,
        routes: example1Routes,
        tabTitle: "Example 1",
        tabIcon: Icons.ac_unit,
        ),
        ExampleTabs.example2: TabRoutesDefinition(
        initialRoute: Example2Routes.root,
        routes: example2Routes,
        tabTitle: "Example 2",
        tabIcon: Icons.accessibility_new,
        ),
        ExampleTabs.example3: TabRoutesDefinition(
        initialRoute: Example2Routes.root,
        routes: example3Routes,
        tabTitle: "Example 3",
        tabIcon: Icons.adb,
        ),
    },
)

Changing Layout is super easy!

Network

BuiltTabNavigator(
    activeTabColor: Colors.red,
    inactiveTabColor: Colors.white,
    bodyBuilder: (context, tabs, tabView) {
        return Column(
        children: <Widget>[
            Material(
            elevation: 9,
            color: Colors.blue[200],
            child: Container(
                height: 100,
                child: Row(
                children: tabs,
                ),
            ),
            ),
            Expanded(
            child: tabView,
            ),
        ],
        );
    },
    tabs: {
        ExampleTabs.example1: TabRoutesDefinition(
        initialRoute: Example1Routes.root,
        routes: example1Routes,
        tabTitle: "Example 1",
        tabIcon: Icons.ac_unit,
        ),
        ExampleTabs.example2: TabRoutesDefinition(
        initialRoute: Example2Routes.root,
        routes: example2Routes,
        tabTitle: "Example 2",
        tabIcon: Icons.accessibility_new,
        ),
        ExampleTabs.example3: TabRoutesDefinition(
        initialRoute: Example2Routes.root,
        routes: example3Routes,
        tabTitle: "Example 3",
        tabIcon: Icons.adb,
        ),
    },
)

Available API Options

  /// Defines default [selectedTab]

  /// Defines the [tabs] of this widget
  /// Each [tab] must define a [TabRoutesDefinition]
  final Map<T, TabRoutesDefinition> tabs;

  /// Defines a [bodyBuilder], if you need something very custom, maybe the tabs located at different position: ie on Top
  /// you can place [tabs] and [tabsViews] in a custom layout arrangement
  final BodyBuilder bodyBuilder;

  /// Builds a custom [tab] widget for each tab, make sure to call the [cb] parameter if you're using a custom [GestureDetector|InkWell]  or whatever widget that could handle touch events
  /// calling [cb] will trigger state build and change tab content as expected
  final TabBuilder<T> tabBuilder;

  /// Defines [Color] used for the tab [title] and [icon] when [tab] is active
  final Color activeTabColor;

  /// Defines [Color] used for the tab [title] and [icon] when [tab] is inactive
  final Color inactiveTabColor;

  /// Defines a tap handler when a [tab]
  final Function(T) tabTap;

  /// Builds a custom [title] [Widget]
  final TitleBuilder<T> titleBuilder;

  /// Builds a custom [icon] [Widget]
  final IconBuilder<T> iconBuilder;

  /// Called everytime a route is being generated, it passes
  /// the actual [RouteSettings],
  /// the tab [T] who owns that navigator,
  /// the actual route [EnumClass] used to produce the page
  /// and the page builder [WidgetBuilder] based on the routes defined
  /// This can be used to return a custom PageRoute Wrapper like `MaterialPageRoute` or wraps the builder with a custom animation, etc
  final OnGenerateRouteFn<T> onGenerateRoute;

  /// Set [activetab], if not defined will default to the firs [tab] key defined at [tabs]
  final T activeTab;

  /// Change defaults tab container background
  final Color tabContainerBackgroundColor;

  /// [didPop] navigationObserver callback
  final void Function(T tab, Route route, Route previousRoute) didPop;

  /// [didPush] navigationObserver callback
  final void Function(T tab, Route route, Route previousRoute) didPush;

  /// [didRemove] navigationObserver callback
  final void Function(T tab, Route route, Route previousRoute) didRemove;

  /// [didReplace] navigationObserver callback
  final void Function(T tab, Route newRoute, Route oldRoute) didReplace;

  /// If [true] , it will implement [WillPopScope] widget for the nested navigation views, if [false],
  /// back navigation will target the root navigator
  /// defaults to [true]
  final bool overridePopBehavior;

  /// Set a custom [height] for the tabs container
  /// defaults to [60]
  /// This property doesnt take any effect if [bodyBuilder] is defined
  final double tabsHeight;

  /// Defines a cutom builder for the widget that wraps each tab content,
  /// This can be useful for bulding a Widget that implements a custom animation
  /// params:
  /// [BuildContext] The current context
  /// [EnumClass] The current `tab` being builded
  /// [bool] if the current `tab` is active
  /// [Widget] The actual content to be wraped, you neend to pass this widget as a child of your Widget implementation
  final TabContentWrapBuilder contentWrapBuilder;

  /// Define a custom duration for the opacity transition implemented
  /// This has no effect if you're implementing a custom [contentWrapBuilder]
  /// Defaults to [Duration(400ms)]
  final Duration contentAnimationDuration;

Screenshots

NetworkNetworkNetwork

You can try the example

Getting Started

This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add built_tab_navigator

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


dependencies:
  built_tab_navigator: ^1.0.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:built_tab_navigator/built_tab_navigator.dart';

example/lib/main.dart

import 'package:built_collection/built_collection.dart';
import 'package:built_tab_navigator/built_tab_navigator.dart';
import 'package:built_value/built_value.dart';
import 'package:flutter/material.dart';

import 'example1_routes.dart';
import 'example2_routes.dart';
import 'example3_routes.dart';

part 'main.g.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: BuiltTabNavigator(
        activeTabColor: Colors.red,
        inactiveTabColor: Colors.white,
        bodyBuilder: (context, tabs, tabView) {
          return Column(
            children: <Widget>[
              Material(
                elevation: 9,
                color: Colors.blue[200],
                child: Container(
                  height: 100,
                  child: Row(
                    children: tabs,
                  ),
                ),
              ),
              Expanded(
                child: tabView,
              ),
            ],
          );
        },
        tabs: {
          ExampleTabs.example1: TabRoutesDefinition(
            initialRoute: Example1Routes.root,
            routes: example1Routes,
            tabTitle: "Example 1",
            tabIcon: Icons.ac_unit,
          ),
          ExampleTabs.example2: TabRoutesDefinition(
            initialRoute: Example2Routes.root,
            routes: example2Routes,
            tabTitle: "Example 2",
            tabIcon: Icons.accessibility_new,
          ),
          ExampleTabs.example3: TabRoutesDefinition(
            initialRoute: Example2Routes.root,
            routes: example3Routes,
            tabTitle: "Example 3",
            tabIcon: Icons.adb,
          ),
        },
      ),
    );
  }
}

class ExampleTabs extends EnumClass {
  static const ExampleTabs example1 = _$example1;
  static const ExampleTabs example2 = _$example2;
  static const ExampleTabs example3 = _$example3;

  const ExampleTabs._(String name) : super(name);

  static BuiltSet<ExampleTabs> get values => _$values;
  static ExampleTabs valueOf(String name) => _$valueOf(name);
}

#fluter  #dart #mobile-apps

Creates A Tab Style UI with Navigation Capabilities

Syncfusion Flutter SignaturePad Library

Syncfusion Flutter SignaturePad library is written in Dart for capturing smooth and more realistic signatures and save it as an image to sync across devices and documents that needs signatures.

Overview

This library is used to capture a signature through drawing gestures. You can use your finger, pen, or mouse on a tablet, touchscreen, etc., to draw your own signature in this SignaturePad widget. The widget also allows you to save a signature as an image, which can be further synchronized with your documents that need the signature.

Disclaimer : This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Free Syncfusion Community license. For more details, please check the LICENSE file.

Flutter signature drawing

SignaturePad features

  • Rich customization - The widget allows you to set the minimum and maximum stroke widths and the stroke color for a signature. Also, you can set the background color of the SignaturePad.
  • More realistic handwritten look and feel - The unique stroke rendering algorithm draws signatures based on the speed of the drawing gestures, along with the minimum and maximum stroke thicknesses, which brings a more realistic, handwritten look and feel to your signatures.
  • Save as image - Save the drawn signature as an image to embed in documents, PDFs, and anything else that supports using images to denote a signature.

Get the demo application

Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub.

Other useful links

Take a look at the following to learn more about the Syncfusion Flutter SignaturePad:

Installation

Install the latest version from pub.dev.

Getting started

Import the following package.

import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';

Add SignaturePad to widget tree

Add the SignaturePad widget as a child of any widget. Here, the SignaturePad widget is added as a child of the Container widget.

@override

  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: SfSignaturePad(),
      ),
    );
  }
  

Add SignaturePad elements

Update elements such as stroke color, minimum stroke width, maximum stroke width, and background color to capture a realistic signature. In the following code example, the SignaturePad is added inside a Container widget to get a size for it.

@override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Center(
        child: Container(
          child: SfSignaturePad(
            minimumStrokeWidth: 1,
            maximumStrokeWidth: 3,
            strokeColor: Colors.blue,
            backgroundColor: Colors.grey,
          ),
          height: 200,
          width: 300,
        ),
      ),
    );
  }

Save the signature as image in android and iOS platforms

You can save the signature drawn in the SignaturePad as an image using the toImage() method, as shown in the following code snippet in the Android and iOS platforms. Since this toImage() method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. Optionally, the pixelRatio parameter may be used to set the pixel ratio of the image. The higher the pixel ratio value, the high-quality picture you get. The default value of the pixel ratio parameter is 1.

@override

  Widget build(BuildContext context) {

    GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();

    return Scaffold(
      body: Column(
        children: [
          Container(
            child: SfSignaturePad(
              key:_signaturePadKey,
              backgroundColor: Colors.grey[200],
            ),
            height: 200,
            width: 300,
          ),
          RaisedButton(
              child: Text("Save As Image"),
              onPressed: () async {
                ui.Image image =
                   await_signaturePadKey.currentState.toImage(pixelRatio: 3);
              }),
        ],
      ),
    );
  }

Save the signature as an image in a web platform

You can save the signature drawn in the SignaturePad as an image using the renderToContext2D() method as shown in the following code snippet. Since this renderToContext2D () method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method.

  @override

  Widget build(BuildContext context) {

    GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();

    return Scaffold(
      body: Column(
        children: [
          Container(
            child: SfSignaturePad(
              key: _signaturePadKey,
              backgroundColor: Colors.grey[200],
            ),
            height: 200,
            width: 300,
          ),
          RaisedButton(
              child: Text("Save As Image"),
              onPressed: () async {
                
				//Get the html canvas context
                final canvas = html.CanvasElement(width: 500, height: 500);
                final context = canvas.context2D;

		        //Get the signature in the canvas context
                _signaturePadKey.currentState.renderToContext2D(context);

		        //Get the image from the canvas context
                final blob = await canvas.toBlob('image/jpeg', 1.0);
                final completer = Completer<Uint8List>();
                final reader = html.FileReader();
                reader.readAsArrayBuffer(blob);
                reader.onLoad.listen((_) => completer.complete(reader.result));
                Uint8List imageData = await completer.future;

              }),
        ],
      ),
    );
  }

Clear the existing signature in SignaturePad

You can clear the signature drawn in the SignaturePad using the clear() method as show in the following code snippet. Since this clear() method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method.

@override
  Widget build(BuildContext context) {
    
	GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
    
	return Scaffold(
      body: Column(
        children: [
          Container(
            child: SfSignaturePad(
              key: _signaturePadKey,
              backgroundColor: Colors.grey[200],
            ),
            height: 200,
            width: 300,
          ),
          RaisedButton(
              child: Text("Clear"),
              onPressed: () async {
                ui.Image image =
                   _signaturePadKey.currentState.clear();
              }),
        ],
      ),
    );
  }

Support and feedback

  • If you have any questions, you can reach out to our Syncfusion support team or post question on our community forums . Submit a feature request or a bug through our feedback portal.
  • To renew your subscription, click renew or contact our sales team at salessupport@syncfusion.com | Toll Free: 1-888-9 DOTNET.

About Syncfusion

Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies.

Today we provide 1,000+ controls and frameworks for web (ASP.NET Core, ASP.NET MVC, ASP.NET WebForms, JavaScript, Angular, React, Vue, and Blazor, mobile (Xamarin, Flutter, UWP, and JavaScript), and desktop development (WinForms, WPF, and UWP). We provide ready-to deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add syncfusion_flutter_signaturepad

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


dependencies:
  syncfusion_flutter_signaturepad: ^19.2.55

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';

example/lib/main.dart

import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';

void main() {
  return runApp(SignaturePadApp());
}

///Renders the SignaturePad widget.
class SignaturePadApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SfSignaturePad Demo',
      home: _MyHomePage(),
    );
  }
}

class _MyHomePage extends StatefulWidget {
  _MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<_MyHomePage> {
  final GlobalKey<SfSignaturePadState> signatureGlobalKey = GlobalKey();

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

  void _handleClearButtonPressed() {
    signatureGlobalKey.currentState!.clear();
  }

  void _handleSaveButtonPressed() async {
    final data =
        await signatureGlobalKey.currentState!.toImage(pixelRatio: 3.0);
    final bytes = await data.toByteData(format: ui.ImageByteFormat.png);
    await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Container(
                color: Colors.grey[300],
                child: Image.memory(bytes!.buffer.asUint8List()),
              ),
            ),
          );
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(
            children: [
          Padding(
              padding: EdgeInsets.all(10),
              child: Container(
                  child: SfSignaturePad(
                      key: signatureGlobalKey,
                      backgroundColor: Colors.white,
                      strokeColor: Colors.black,
                      minimumStrokeWidth: 1.0,
                      maximumStrokeWidth: 4.0),
                  decoration:
                      BoxDecoration(border: Border.all(color: Colors.grey)))),
          SizedBox(height: 10),
          Row(children: <Widget>[
            TextButton(
              child: Text('ToImage'),
              onPressed: _handleSaveButtonPressed,
            ),
            TextButton(
              child: Text('Clear'),
              onPressed: _handleClearButtonPressed,
            )
          ], mainAxisAlignment: MainAxisAlignment.spaceEvenly)
        ],
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center));
  }
}

#fluter  #dart #mobile-apps

Syncfusion Flutter SignaturePad Library

Mobile Secure Keyboard to Prevent KeyLogger Attack and Screen Capture

Flutter Secure Keyboard .Mobile secure keyboard to prevent KeyLogger attack and screen capture.

Screenshots

| |

Getting started

To use this plugin, add flutter_secure_keyboard as a dependency in your pubspec.yaml file. For example:

dependencies:
  flutter_secure_keyboard: ^2.2.2

Examples

class WithSecureKeyboardExample extends StatefulWidget {
  @override
  _WithSecureKeyboardExampleState createState() => _WithSecureKeyboardExampleState();
}

class _WithSecureKeyboardExampleState extends State<WithSecureKeyboardExample> {
  final _secureKeyboardController = SecureKeyboardController();

  final _passwordEditor = TextEditingController();
  final _passwordTextFieldFocusNode = FocusNode();

  final _pinCodeEditor = TextEditingController();
  final _pinCodeTextFieldFocusNode = FocusNode();

  @override
  Widget build(BuildContext context) {
    // Set the WithSecureKeyboard widget as the top-level widget
    // in the build function so that the secure keyboard works properly.
    return WithSecureKeyboard(
      controller: _secureKeyboardController,
      child: Scaffold(
        appBar: AppBar(title: Text('with_secure_keyboard_example')),
        body: _buildContentView()
      ),
    );
  }

  Widget _buildContentView() {
    // We recommend using the ListView widget to prevent widget overflow.
    return ListView(
      padding: const EdgeInsets.all(8.0),
      children: [
        _buildPasswordTextField(),
        SizedBox(height: 12.0),
        _buildPinCodeTextField()
      ],
    );
  }

  Widget _buildPasswordTextField() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Password'),
        TextFormField(
          controller: _passwordEditor,
          focusNode: _passwordTextFieldFocusNode,
          // We recommended to set false to prevent the software keyboard from opening.
          enableInteractiveSelection: false,
          obscureText: true,
          onTap: () {
            _secureKeyboardController.show(
              type: SecureKeyboardType.ALPHA_NUMERIC,
              focusNode: _passwordTextFieldFocusNode,
              initText: _passwordEditor.text,
              hintText: 'password',
              // Use onCharCodesChanged to have text entered in real time.
              onCharCodesChanged: (List<int> charCodes) {
                _passwordEditor.text = String.fromCharCodes(charCodes);
              }
            );
          },
        ),
      ],
    );
  }

  Widget _buildPinCodeTextField() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('PinCode'),
        TextFormField(
          controller: _pinCodeEditor,
          focusNode: _pinCodeTextFieldFocusNode,
          // We recommended to set false to prevent the software keyboard from opening.
          enableInteractiveSelection: false,
          obscureText: true,
          onTap: () {
            _secureKeyboardController.show(
              type: SecureKeyboardType.NUMERIC,
              focusNode: _pinCodeTextFieldFocusNode,
              initText: _pinCodeEditor.text,
              hintText: 'pinCode',
              // Use onDoneKeyPressed to allow text to be entered when you press the done key,
              // or to do something like encryption.
              onDoneKeyPressed: (List<int> charCodes) {
                _pinCodeEditor.text = String.fromCharCodes(charCodes);
              }
            );
          },
        ),
      ],
    );
  }
}

Package

  • WithSecureKeyboard - A widget that implements a secure keyboard with controller.
  • SecureKeyboardController - Controller to check or control the state of the secure keyboard.

Note: The parameters marked with an asterisk(*) are required.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_secure_keyboard

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


dependencies:
  flutter_secure_keyboard: ^2.2.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_secure_keyboard/flutter_secure_keyboard.dart';

example/lib/main.dart

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

void main() => runApp(ExampleApp());

class ExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: WithSecureKeyboardExample()
    );
  }
}

class WithSecureKeyboardExample extends StatefulWidget {
  @override
  _WithSecureKeyboardExampleState createState() => _WithSecureKeyboardExampleState();
}

class _WithSecureKeyboardExampleState extends State<WithSecureKeyboardExample> {
  final _secureKeyboardController = SecureKeyboardController();

  final _passwordEditor = TextEditingController();
  final _passwordTextFieldFocusNode = FocusNode();

  final _pinCodeEditor = TextEditingController();
  final _pinCodeTextFieldFocusNode = FocusNode();

  @override
  Widget build(BuildContext context) {
    // Set the WithSecureKeyboard widget as the top-level widget
    // in the build function so that the secure keyboard works properly.
    return WithSecureKeyboard(
      controller: _secureKeyboardController,
      child: Scaffold(
        appBar: AppBar(title: Text('with_secure_keyboard_example')),
        body: _buildContentView()
      ),
    );
  }

  Widget _buildContentView() {
    // We recommend using the ListView widget to prevent widget overflow.
    return ListView(
      padding: const EdgeInsets.all(8.0),
      children: [
        _buildPasswordTextField(),
        SizedBox(height: 12.0),
        _buildPinCodeTextField()
      ],
    );
  }

  Widget _buildPasswordTextField() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Password'),
        TextFormField(
          controller: _passwordEditor,
          focusNode: _passwordTextFieldFocusNode,
          // We recommended to set false to prevent the software keyboard from opening.
          enableInteractiveSelection: false,
          obscureText: true,
          onTap: () {
            _secureKeyboardController.show(
              type: SecureKeyboardType.ALPHA_NUMERIC,
              focusNode: _passwordTextFieldFocusNode,
              initText: _passwordEditor.text,
              hintText: 'password',
              // Use onCharCodesChanged to have text entered in real time.
              onCharCodesChanged: (List<int> charCodes) {
                _passwordEditor.text = String.fromCharCodes(charCodes);
              }
            );
          },
        ),
      ],
    );
  }

  Widget _buildPinCodeTextField() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('PinCode'),
        TextFormField(
          controller: _pinCodeEditor,
          focusNode: _pinCodeTextFieldFocusNode,
          // We recommended to set false to prevent the software keyboard from opening.
          enableInteractiveSelection: false,
          obscureText: true,
          onTap: () {
            _secureKeyboardController.show(
              type: SecureKeyboardType.NUMERIC,
              focusNode: _pinCodeTextFieldFocusNode,
              initText: _pinCodeEditor.text,
              hintText: 'pinCode',
              // Use onDoneKeyPressed to allow text to be entered when you press the done key,
              // or to do something like encryption.
              onDoneKeyPressed: (List<int> charCodes) {
                _pinCodeEditor.text = String.fromCharCodes(charCodes);
              }
            );
          },
        ),
      ],
    );
  }
}

#fluter  #dart #mobile-apps

Mobile Secure Keyboard to Prevent KeyLogger Attack and Screen Capture

Create Beautiful and Customized Tab/Toggle Widget on Flutter

Flutter Tab Toggle .A Beautiful and Simple Tab/Toggle switch widget. It can be fully customized with desired icons, width, colors, text, corner radius etc. It also maintains selection state.

Getting Started

In the pubspec.yaml of your flutter project, add the following dependency:

dependencies:
  ...
  flutter_toggle_tab: "^latestVersion"

Import it:

import 'package:flutter_toggle_tab/flutter_toggle_tab.dart';

Usage Examples

Basic tab/toggle switch

// Here default theme colors are used for activeBgColor, activeFgColor, inactiveBgColor and inactiveFgColor
FlutterToggleTab(  
  // width in percent, to set full width just set to 100  
  width: 90,  
  borderRadius: 30,  
  height: 50,  
  initialIndex:0, 
  selectedColors: [Colors.blue],  
  selectedTextStyle: TextStyle(  
    color: Colors.white,
    fontSize: 18,
    fontWeight: FontWeight.w700),
  unSelectedTextStyle: TextStyle(  
    color: Colors.black87,
    fontSize: 14,
    fontWeight: FontWeight.w500),
  labels: ["Tab A (10)", "Tab B (6)", "Tab C (9)"],  
  selectedLabelIndex: (index) {  
	print("Selected Index $index");
  },  
),

Farmers Market Finder Demo

Basic tab/toggle switch with Icon

FlutterToggleTab(  
  width: 50,  
  borderRadius: 15,  
  initialIndex:0, 
  selectedTextStyle: TextStyle(
    color: Colors.white,
    fontSize: 18,
    fontWeight: FontWeight.w600),
  unSelectedTextStyle: TextStyle(
    color: Colors.blue,
    fontSize: 14,
    fontWeight: FontWeight.w400),
  labels: ["Male","Female"],  
  icons: [Icons.person,Icons.pregnant_woman],  
  selectedLabelIndex: (index) {  
	print("Selected Index $index");
  },  
),

Farmers Market Finder Demo

Basic tab/toggle switch with Icon Only

FlutterToggleTab(  
  width: 40,  
  borderRadius: 15,  
  initialIndex:0, 
  selectedTextStyle: TextStyle(
    color: Colors.white,
    fontSize: 18,
    fontWeight: FontWeight.w600),
  unSelectedTextStyle: TextStyle(
    color: Colors.grey,
    fontSize: 14,
    fontWeight: FontWeight.w400),
  labels: ["",""],  
  icons: [Icons.person,Icons.pregnant_woman], 
  selectedLabelIndex: (index) { 
	print("Selected Index $index");
  },

Farmers Market Finder Demo

Update selected tab Programmatically

var _selectedIndex= 0 // you can change selected with update this 
FlutterToggleTab(  
  width: 40,  
  borderRadius: 15,
  initialIndex:0, 
  selectedIndex: _selectedIndex,  
  selectedTextStyle: TextStyle(
    color: Colors.white,
    fontSize: 18,
    fontWeight: FontWeight.w600),
  unSelectedTextStyle: TextStyle(
    color: Colors.grey,
    fontSize: 14,
    fontWeight: FontWeight.w400),
  labels: ["",""],  
  icons: [Icons.person,Icons.pregnant_woman], 
  selectedLabelIndex: (index) {  
	setState(() {
	  _selectedIndex = index;
	  print("Selected Index $index");
	});
  },    

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_toggle_tab

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


dependencies:
  flutter_toggle_tab: ^1.1.6

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_toggle_tab/flutter_toggle_tab.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_toggle_tab/flutter_toggle_tab.dart';
import 'package:flutter_toggle_tab/helper.dart';

void main() {
  runApp(MyApp());
  SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      statusBarIconBrightness: Brightness.dark));
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
        // This makes the visual density adapt to the platform that you run
        // the app on. For desktop platforms, the controls will be smaller and
        // closer together (more dense) than on mobile platforms.
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Toggle Tab Sample Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _tabTextIndexSelected = 0;
  var _tabTextIconIndexSelected = 0;
  var _tabIconIndexSelected = 0;
  var _tabSelectedIndexSelected = 0;

  var _listTextTabToggle = ["Tab A (10)", "Tab B (6)", "Tab C (9)"];
  var _listTextSelectedToggle = [
    "Select A (10)",
    "Select B (6)",
    "Select C (9)"
  ];
  var _listIconTabToggle = [
    Icons.person,
    Icons.pregnant_woman,
  ];
  var _listGenderText = ["Male", "Female"];
  var _listGenderEmpty = ["", ""];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter Tab Toggle"),
        elevation: 0,
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Basic Tab Toggle",
              style: TextStyle(fontSize: 20, fontStyle: FontStyle.italic),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            FlutterToggleTab(
              // width in percent
              width: 90,
              borderRadius: 30,
              height: 50,
              initialIndex: 0,
              selectedBackgroundColors: [Colors.blue, Colors.blueAccent],
              selectedTextStyle: TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                  fontWeight: FontWeight.w700),
              unSelectedTextStyle: TextStyle(
                  color: Colors.black87,
                  fontSize: 14,
                  fontWeight: FontWeight.w500),
              labels: _listTextTabToggle,
              selectedLabelIndex: (index) {
                setState(() {
                  _tabTextIndexSelected = index;
                });
              },
              isScroll: false,
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Index selected : $_tabTextIndexSelected",
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Divider(
              thickness: 2,
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Text With Icon",
              style: TextStyle(fontSize: 20, fontStyle: FontStyle.italic),
            ),
            Padding(
              padding: EdgeInsets.all(16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    "Select your sex : ",
                    style: TextStyle(fontSize: 20),
                  ),
                  FlutterToggleTab(
                    width: 50,
                    borderRadius: 15,
                    initialIndex: 0,
                    selectedTextStyle: TextStyle(
                        color: Colors.white,
                        fontSize: 18,
                        fontWeight: FontWeight.w600),
                    unSelectedTextStyle: TextStyle(
                        color: Colors.blue,
                        fontSize: 14,
                        fontWeight: FontWeight.w400),
                    labels: _listGenderText,
                    icons: _listIconTabToggle,
                    selectedLabelIndex: (index) {
                      setState(() {
                        _tabTextIconIndexSelected = index;
                      });
                    },
                  ),
                ],
              ),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Selected sex : ${_listGenderText[_tabTextIconIndexSelected]} ",
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Divider(
              thickness: 2,
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "With Icon Only and Implement margin for selected item",
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 20, fontStyle: FontStyle.italic),
            ),
            Padding(
              padding: EdgeInsets.all(16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    "Select your sex : ",
                    style: TextStyle(fontSize: 20),
                  ),
                  FlutterToggleTab(
                    width: 40,
                    borderRadius: 15,
                    initialIndex: _tabIconIndexSelected,
                    selectedTextStyle: TextStyle(
                        color: Colors.white,
                        fontSize: 18,
                        fontWeight: FontWeight.w600),
                    unSelectedTextStyle: TextStyle(
                        color: Colors.grey,
                        fontSize: 14,
                        fontWeight: FontWeight.w400),
                    labels: _listGenderEmpty,
                    icons: _listIconTabToggle,
                    selectedLabelIndex: (index) {
                      setState(() {
                        _tabIconIndexSelected = index;
                      });
                    },
                    marginSelected:
                        EdgeInsets.symmetric(horizontal: 4, vertical: 4),
                  ),
                ],
              ),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Selected sex index: $_tabIconIndexSelected ",
              style: TextStyle(fontSize: 20),
            ),
            Divider(
              thickness: 2,
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Update selected programmatically  ",
              style: TextStyle(fontSize: 20, fontStyle: FontStyle.italic),
            ),
            Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    "Select your sex : ",
                    style: TextStyle(fontSize: 20),
                  ),
                  FlutterToggleTab(
                    width: 90,
                    borderRadius: 15,
                    initialIndex: 0,
                    selectedIndex: _tabSelectedIndexSelected,
                    selectedTextStyle: TextStyle(
                        color: Colors.white,
                        fontSize: 18,
                        fontWeight: FontWeight.w600),
                    unSelectedTextStyle: TextStyle(
                        color: Colors.grey,
                        fontSize: 14,
                        fontWeight: FontWeight.w400),
                    labels: _listTextSelectedToggle,
                    selectedLabelIndex: (index) {
                      setState(() {
                        _tabSelectedIndexSelected = index;
                      });
                    },
                  ),
                  TextButton(
                    onPressed: () {
                      setState(() {
                        _tabSelectedIndexSelected = 2;
                      });
                    },
                    child: Text("Select C"),
                  )
                ],
              ),
            ),
            SizedBox(
              height: heightInPercent(3, context),
            ),
            Text(
              "Selected sex index: $_tabIconIndexSelected ",
              style: TextStyle(fontSize: 20),
            ),
          ],
        ),
      ),
      // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

#fluter  #dart #mobile-apps

Create Beautiful and Customized Tab/Toggle Widget on Flutter

Flutter Package to Parse ICalendar (.ics) Files

icalendar_parser .Package to parse iCalendar (.ics) files written in pure Dart.

Implementation of AnyFetch's ics-parser in JavaScript.

Getting Started

Add icalendar_parser to your pubspec.yaml:

icalendar_parser: any

How to use

You can refer to the example/ folder for a complete example implemented in Flutter.

Constructor

ICalendar.fromString

Warning: For unknown reason the command dart test on GitHub Actions will generate errors so try to prefer using ICalendar.fromLines in your unit tests.

import 'package:flutter/services.dart' show rootBundle;
import 'package:icalendar_parser/icalendar_parser.dart';

final icsString = await rootBundle.loadString('assets/your_file.ics');
final iCalendar = ICalendar.fromString(icsString);

ICalendar.fromLines

final icsLines = await File('your_file.ics').readAsLines();
final iCalendar = ICalendar.fromLines(lines);

Other methods

ICalendar.registerField

With this method you can add fields that are not already supported (check Supported Properties) to the parsing and you can specify a custom function to parse its content :

ICalendar.registerField(field: 'TEST');

ICalendar.registerField(
    field: 'TEST2',
    function: (value, params, event, lastEvent) {
        lastEvent['test2'] = 'test';
        return lastEvent;
    },
);

ICalendar.unregisterField

With this method you can remove parsed fields to ignore them in your file :

ICalendar.unregisterField('TEST');

ICalendar.toJson

Convert [ICalendar] object to a Map<String, dynamic> containing all its data, formatted into a valid JSON Map<String, dynamic>.

final icsObj = ICalendar.fromLines(File('assets/my_file.ics').readAsLinesSync());
print(jsonEncode(icsObj.toJson()));

Input

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:uid1@example.com
DTSTAMP:19970714T170000Z
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
DTSTART:19970714T170000Z
DTEND:19970715T035959Z
SUMMARY:Bastille Day Party
GEO:48.85299;2.36885
END:VEVENT
END:VCALENDAR

Output

{
   "version":"2.0",
   "prodid":"-//hacksw/handcal//NONSGML v1.0//EN",
   "calscale":"GREGORIAN",
   "method":"PUBLISH",
   "data":[
      {
         "type":"VEVENT",
         "uid":"uid1@example.com",
         "dtstamp":"1997-07-14T17:00:00.000Z",
         "organizer":{
            "name":"John Doe",
            "mail":"john.doe@example.com"
         },
         "dtstart":"1997-07-14T17:00:00.000Z",
         "dtend":"1997-07-15T03:59:59.000Z",
         "summary":"Bastille Day Party",
         "geo":{
            "latitude":48.85299,
            "longitude":2.36885
         }
      }
   ]
}

Supported Properties

  • VERSION
  • PRODID
  • CALSCALE
  • METHOD
  • COMPONENT:BEGIN
  • COMPONENT:END
  • DTSTART
  • DTEND
  • DTSTAMP
  • TRIGGER
  • LAST-MODIFIED
  • COMPLETED
  • DUE
  • UID
  • SUMMARY
  • DESCRIPTION
  • LOCATION
  • URL
  • ORGANIZER
  • GEO
  • CATEGORIES
  • ATTENDEE
  • ACTION
  • STATUS
  • SEQUENCE
  • REPEAT
  • RRULE

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add icalendar_parser

With Flutter:

 $ flutter pub add icalendar_parser

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


dependencies:
  icalendar_parser: ^0.8.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:icalendar_parser/icalendar_parser.dart';

example/lib/main.dart

import 'dart:core';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:icalendar_parser/icalendar_parser.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ICalendar _iCalendar;
  bool _isLoading = false;

  Future<void> _getAssetsFile(String assetName) async {
    setState(() => _isLoading = true);
    try {
      final directory = await getTemporaryDirectory();
      final path = p.join(directory.path, assetName);
      final data = await rootBundle.load('assets/$assetName');
      final bytes =
          data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
      final file = await File(path).writeAsBytes(bytes);
      final lines = await file.readAsLines();
      setState(() {
        _iCalendar = ICalendar.fromLines(lines);
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
      throw 'Error: $e';
    }
  }

  Widget _generateTextContent() {
    const style = TextStyle(color: Colors.black);
    return RichText(
      text: TextSpan(
        children: [
          TextSpan(
              text: 'VERSION: ${_iCalendar.version}\n',
              style: style.copyWith(fontWeight: FontWeight.bold)),
          TextSpan(
              text: 'PRODID: ${_iCalendar.prodid}\n',
              style: style.copyWith(fontWeight: FontWeight.bold)),
          TextSpan(
              text: 'CALSCALE: ${_iCalendar.calscale}\n',
              style: style.copyWith(fontWeight: FontWeight.bold)),
          TextSpan(
              text: 'METHOD: ${_iCalendar.method}\n',
              style: style.copyWith(fontWeight: FontWeight.bold)),
          TextSpan(
              children: _iCalendar.data
                  .map((e) => TextSpan(
                        children: e.keys
                            .map((f) => TextSpan(children: [
                                  TextSpan(
                                      text: '${f.toUpperCase()}: ',
                                      style: style.copyWith(
                                          fontWeight: FontWeight.bold)),
                                  TextSpan(text: '${e[f]}\n')
                                ]))
                            .toList(),
                      ))
                  .toList()),
        ],
        style: style,
      ),
    );
  }

  Future<void> _getAssets(String assetName) async {
    setState(() => _isLoading = true);
    try {
      final icsString = await rootBundle.loadString('assets/$assetName');
      final iCalendar = ICalendar.fromString(icsString);
      setState(() {
        _iCalendar = iCalendar;
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
      throw 'Error: $e';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            if (_isLoading || _iCalendar == null)
              const Center(child: CircularProgressIndicator())
            else
              _generateTextContent(),
            ElevatedButton(
              onPressed: () => _getAssetsFile('calendar.ics'),
              child: const Text('Load File 1'),
            ),
            ElevatedButton(
              onPressed: () => _getAssetsFile('calendar2.ics'),
              child: const Text('Load File 2'),
            ),
            ElevatedButton(
              onPressed: () => _getAssets('calendar.ics'),
              child: const Text('Load String 1'),
            ),
            ElevatedButton(
              onPressed: () => _getAssetsFile('calendar3.ics'),
              child: const Text('Load File 3'),
            ),
          ],
        ),
      ),
    );
  }
}

#fluter  #dart #mobile-apps

Flutter Package to Parse ICalendar (.ics) Files