Categories
Flutter

Push Notification in Flutter using Firebase

In this article, you will learn about how to integrate Firebase Cloud Messaging (FCM) with a Flutter mobile application.

If you want to install Flutter in your environment follow this article: Flutter Setup

Create Flutter project

To create a new project in Flutter execute the following command line in the terminal.

flutter create tryoutflutter

We are using tryoutflutter as the project name. It will create a folder with the name of the project. Inside the project, the files structure should like this:

Let’s look at the folders and files in the project.

android folder: this includes specific files to configure our Android application.

  • To get special permissions,
  • Firebase configuration,
  • To set a logo, splash screen, display name, etc,.

iOS folder, this includes specific files to configure our iOS application.

Build folder, this includes the debug application build APK. This will automatically create once you run the project.

lib folder, this includes Dart files. In this folder, we need to write and manage Dart code.

pubspec.yaml, in this file we can manage the packages (libraries), assets (images, icons, etc,.) that we will use for our application.

Create Firebase Project

Follow the steps to create a new firebase project,

  • Goto Firebase and login with your Google account
  • Then navigate to the Firebase console in that you can create a project
  • Give a name to your project. If you want Google Analytics for your project, enable it otherwise it’s optional only. Wait for a moment once your project is ready, we can start setup.

Android setup

  • Open your Firebase project, click the Android icon or add app button, it will return a form. Fill out the form to register the app.
  • To know the package name of your application look into the androidmanifest.xml or app level build.gradle file
Paste the file in this location PATH: <projectname>/android/app/’Paste the file here’
  • Firebase returns a config file in JSON format. Download and place in your project folder, in the following path <projectName>/android/app.
  • Click the next button. Then add Firebase SDK by following the Firebase assistant.
  • Add this dependency implementation 'com.google.firebase:firebase-messaging:' in your app level build.gradle file, to enable receive messages in the background. Check this link to get the latest version.

iOS setup

  • Open your Firebase project, click the iOS button or add app button, it will return a form. Fill out the form to register the app.
  • To know the app bundle id, open your project in Xcode. Select runner in file navigator and select runner in the target also. Copy the bundle id from the identity section.
To know Bundle id from Xcode
  • Now you have registered your app in Firebase, then skip the upcoming steps. That’s only required if you use native development.

Follow the steps to enable APN’s:

  • To send push notification to iOS devices have to enable the Apple Push Notification service to setup that follow this documentation,
  • After the creation of an identifier key and a provisioning profile in your developer account, add your team id in Firebase iOS Flutter project settings.
  • Download the latest config file. Place the file in your Flutter project folder, in the following path <project name>/ios/Runner.xcworkspace
  • Open the project in Xcode, select Runner in the project navigator. Select capability add following capabilities, Push notifications and background modes.
  • Also enable background fetch and remote notifications under the background modes.

Compose notification

Android setup is done, if you send any notification from Firebase Cloud Messaging (FCM), the devices will be able to receive for (Android only). For iOS only we need to get the permissions, refer to the code attached below.

Let see how to compose notification to send,

  • Navigate to Cloud Messaging in your Firebase project, then Click the new notification.
  • Give the title and select the target applications.
  • Click the review button, and publish the notification. It will send a notification to all devices.

Configuring FCM

Let see how to send a notification to a specific device and to a group of devices which is subscribed to a topic.

Now it’s time to code, add this package firebase_messaging: in pubspec.yaml file under the dependencies.

To receive notification in device

import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; //Imported firebase_messaging package

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

  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final FirebaseMessaging _fcm = FirebaseMessaging(); // Here _fcm is instance of FirebaseMessaging 
  @override
  void initState() {
    super.initState();
    if (Platform.isIOS) {
      _fcm.requestNotificationPermissions(IosNotificationSettings()); //Geting permission for iOS only
    }
    _fcm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message"); //It fires when the app is open, if notification received
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message"); //If notification received, it fires when app is running in backgroud
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message"); // If notification received, it fires when app is fully terminated
      },
    ); //With 
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container()
    );
  }
}

Send notification to a topic

It’s simple, we have to configure in both composing the notification as well as also while receiving the notification in the device.

Type your topic name in the message topic field, with this reference it will send the notification to all subscribed devices.

Subscribe to an topic

import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; //Imported firebase_messaging package

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

  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final FirebaseMessaging _fcm = FirebaseMessaging(); // Here _fcm is instance of FirebaseMessaging 
  @override
  void initState() {
    super.initState();
    if (Platform.isIOS) {
      _fcm.requestNotificationPermissions(IosNotificationSettings()); //Geting permission for iOS only
    }
    _fcm.subscribeToTopic('topic'); // Topic subscribed
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container()
    );
  }
}

Send notification to a specific device

FCM provides unique tokens to all devices, the code below will show how to get the device token.

getToken() async {
    //print(await _fcm.getToken());
    //The getToken() will return a Future string, so async is used 
    return await _fcm.getToken(); 
  }

We need to set up a server to send a notification to specific devices, store the device token in the database, and use it with your logic. Refer to this documentation for more detail about the setup.

To send dynamic notification using FCM, have to go for Firebase Cloud functions.

I have attached the complete code in this GitHub repo.

Categories
Flutter

Flutter Setup

HelloWorld! In this article, you will learn about how to setup Flutter in Windows, Linux, and Mac OS through step by step simple procedure. Let’s get started,

Prerequisites

  • Android Studio
  • X Code and CocoaPods only for Mac OS
  • VS Code editor or any other editor. VS code editor is recommended because it has the Flutter plugin which helps in fast debugging
  • Git version control
  • Install Flutter and Dart plugin in Android studio.

Setup for Windows

Minimum requirements

  • Require at least Windows 7 SP1 or later
  • Disk space around 1.3 GB required only for flutter setup
  • Flutter depends on Windows PowerShell 5.0 or newer (this is pre-installed with windows 10), if your using windows 10 don’t worry about it and if you want to clone the GitHub repository have to install Git

To download Git for Windows x86 and Windows x64.

Download and setup Flutter

  • Download the Flutter SDK from this link or clone the Flutter’s repository by executing the following command in your terminal,
git clone https://github.com/flutter/flutter.git -b stable
  • After completion of the download, extract the file in your desired location for example (C:\flutter),
  • Don’t place the Flutter SDK inside the program files it may require some elevated privileges.

Next step is to add the Flutter path in your system environment variable, by the following steps. This enables us to execute all Flutter commands from the command prompt:

  1. From the Windows search look for ‘env’, then select edit environment variables for your account
  2. In under the user variables check there is any variable named Path if exists paste the Flutter SDK full path, example (C:\Flutter\bin) in values if any values exist separate between the two values using ‘;’, if not exists create a new variable name it as path and paste the Flutter SDK location in the value.

Last step in Flutter setup

Open the command prompt, run flutter doctor a command will let you know there are any dependencies to complete the Flutter setup.

If it shows an error like Android license status unknown, you can resolve this by executing the following command line in your terminal.

flutter doctor --android-licenses

If you get logs like the following screenshot you have done.

Setup for Linux

Minimum requirements

  • Minimum disk space around 600MB
  • Operating system Linux 64Bit

Download and setup Flutter

  • Download the Flutter SDK from this link or clone the Flutter’s repository by executing the following command in your terminal,
git clone https://github.com/flutter/flutter.git -b stable --depth 1
  • After completion of the download, extract the file in your desired location for example (/home/`userName`/flutter)
  • Add Flutter tool to your path, open the .bashrc file from your home folder then paste the following line
  • By default the file was been hidden, so check the show hidden files from menu, then open and the add this line
export PATH="$PATH:`pwd`/flutter/bin"

Last step in Flutter setup

Open the terminal, run flutter doctor a command will let you know there are any dependencies to complete the Flutter setup.

If it shows an error like Android license status unknown, you can resolve this by executing the following command line in your terminal

flutter doctor --android-licenses

If you get logs like the following screenshot you have done.

Setup for Mac OS

Minimum requirements

  • Minimum disk space around 2.8Gb and
  • Operating system Linux 64Bit

Download and setup Flutter

  • Download the Flutter SDK from this link or clone the Flutter’s repository from this link,
git clone https://github.com/flutter/flutter.git -b stable --depth 1
  • After completion of the download, unzip the file in the desired location for example (/home/`userName`/flutter)
  • To add the Flutter tool to your path, before that by typing echo $SHELL it will show what shell you are using
  • If you are using the Z shell (ZSH) edit ./zshrc file which is located in your home folder. command + shift + h a shortcut to navigate Home folder in Finder
  • If you’re using Bash shell, edit $HOME/.bashrc or $HOME/.bash_profile. Next is add the following line in the corresponding file by using editor

Last step in Flutter setup

Open the terminal, run flutter doctor a command will let you know there are any dependencies to complete the Flutter setup.

If it shows an error like Android license status unknown, you can resolve this by executing the following command line in your terminal

flutter doctor --android-licenses

If you get logs like the following screenshot you have done.

Categories
Elasticsearch

Shrinking indices in Elasticsearch

Optimizing Elasticsearch: How Many Shards per Index? | Qbox HES

The Problem

Today, we started receiving the following error from our production Elasticsearch cluster when a new index was about to be created:

{
  "error": {
    "root_cause": [
      {
        "type": "validation_exception",
        "reason": "Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [991]/[1000] maximum shards open;"
      }
    ],
    "type": "validation_exception",
    "reason": "Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [991]/[1000] maximum shards open;"
  },
  "status": 400
}

The error description was obvious that we would breach the shard limit of 1,000 when creating a new index.

Confirming the number from the error message using _cat/shards endpoint, we see that we had 991 shards in our only data node.

$ curl -s https://<aws_es_url>.es.amazonaws.com:443/_cat/shards | wc -l
991

We had about 99 indices and each index had 5 shards with one replica which contributes to 5 shards as well. So a total of 10 shards per index. We can confirm that by checking the index endpoint:

$ curl -s https://<aws_es_url>.es.amazonaws.com:443/<index_name>?pretty"

which shows the following output (shortened for brevity):

{
  "settings": {
    "number_of_shards": "5",
    "number_of_replicas": "1"
  }
}

Looking around in AWS help docs, they have suggested three solutions:

Suggested fixes

The 7.x versions of Elasticsearch have a default setting of no more than 1,000 shards per node. Elasticsearch throws an error if a request, such as creating a new index, would cause you to exceed this limit. If you encounter this error, you have several options:

  • Add more data nodes to the cluster.
  • Increase the _cluster/settings/cluster.max_shards_per_node setting.
  • Use the _shrink API to reduce the number of shards on the node.

We chose the shrink option because all our indices are small enough that they do not need 5 shards.

How to Shrink?

It is a 3 step process:

Step 1: Block writes on the current index

$ curl -XPUT -H 'Content-Type: application/json' https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>/_settings -d'{
  "settings": {
    "index.number_of_replicas": 0,                                
    "index.routing.allocation.require._name": "shrink_node_name", 
    "index.blocks.write": true                                    
  }
}'

Step 2: Start shrinking with the new shard count

$ curl -XPOST -H 'Content-Type: application/json' https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>/_shrink/<new_index_name> -d'{
  "settings": {
    "index.number_of_replicas": 1,
    "index.number_of_shards": 1, 
    "index.routing.allocation.require._name": null,
    "index.blocks.write": null
  }
}'

You can track the progress of the shrinking via the /_cat/recovery endpoint. Once the shrinking is complete, you can verify the document count via the _cat/indices endpoint.

Once you are happy with the shrinking, go to the next step.

Step 3: Delete the old index

$ curl -XDELETE https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>

You can run the above commands for multiple indices through a shell script like below (place the index names in /tmp/indices.txt as one index name per line):

while read source; do
   <curl command>
done </tmp/indices.txt

Permanent Fix

All the above 3 steps only fixes the existing indices. We’ll need to make some code changes to ensure new indices created from now on is also created with the new setting of one shard.

Include settings.number_of_shards and settings.number_of_replicas in the request payload along with mappings when creating a new index. PHP code for reference:

[
    'mappings' => [
        'properties' => [
            .......
        ],
    ],
    'settings' => [
        'number_of_shards' => 1,
        'number_of_replicas' => 1,
    ]
];

You are now done! 👏

You have successfully fixed both existing indices and new indices.

Further Reading

Categories
AWS

Static Websites with AWS CloudFront and S3

Amazon S3 + Amazon CloudFront: A Match Made in the Cloud | Networking &  Content Delivery

Why CloudFront & S3 is better for hosting static sites?

AWS CloudFront is a CDN that can be used to serve static HTML sites backed by S3 storage.

S3 storage is very cheap. Combined with CloudFront, you can make your sites serve in low latency speeds.

Deploy to S3 and start CloudFront cache invalidation

Why automated deployment?

Automated deployments allows your changes to be made available on the live site instantly and automatically whenever you push to the repository.

You can forget about copying the code manually and uploading to your S3 bucket.

Why trigger cache invalidation?

Once the files are copied to S3, we need to trigger an invalidation for the cache in CloudFront. Otherwise, CloudFront will continue to server the old content from its cache.

We’ll see examples and code snippets for both Bitbucket and Github below.

Using Bitbucket Pipelines

Pipelines is Bitbucket’s CI/CD tool.

Steps to setup deployment:

  • Create a file named bitbucket-pipelines.yml in the project directory.
  • Paste the below code in the file and give your AWS_Access_key_ID, AWS_Secret_access_key, $CloudFront_Distribution_Id values through
  • After adding your AWS key save and push your changes into the bitbucket repo, it is done!
image: node:10.15.0

pipelines:
  default:
    - step:
        name: Deploy to S3
        deployment: production
        script:
          - pipe: atlassian/aws-s3-deploy:0.4.4
            variables:
              AWS_ACCESS_KEY_ID: $AWS_Access_key_ID
              AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key
              AWS_DEFAULT_REGION: 'us-east-1'
              S3_BUCKET: 'jiga.com.au'
              LOCAL_PATH: $BITBUCKET_CLONE_DIR
              ACL: 'public-read'
    - step:
        name: Invalidate CloudFront cache
        script:
          - pipe: atlassian/aws-cloudfront-invalidate:0.3.3
            variables:
              AWS_ACCESS_KEY_ID: $AWS_Access_key_ID
              AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key
              AWS_DEFAULT_REGION: 'us-east-1'
              DISTRIBUTION_ID: $CloudFront_Distribution_Id

You can add the variables directly in the YML file but it is recommended to add them through ‘Repository variables’ under Pipelines Settings in Repository settings:

Using GitHub Actions

Actions is GitHub’s CI/CD tool.

Steps to setup deployment:

  • Create a file deploy-to-s3.yml in the project directory.
  • Add the required variables to secrets
  • After adding your secrets, push your changes into the Github repo and see the magic!
name: Deploy Website
'on':
  push:
    branches:
      - master
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Deploy to S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: '--acl public-read --delete'
        env:
          AWS_S3_BUCKET: '${{ secrets.AWS_PRODUCTION_BUCKET_NAME }}'
          AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
          AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
          AWS_REGION: '${{ secrets.AWS_REGION }}'
          SOURCE_DIR: build
      - name: Invalidate CloudFront Cache
        uses: awact/cloudfront-action@master
        env:
          SOURCE_PATH: ./public
          AWS_REGION: us-east-1
          AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
          AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
          DISTRIBUTION_ID: '${{ secrets.DISTRIBUTION_ID }}'
 

Now that you know how to automatically deploy to S3 on every change to the repository, you can learn some two tricks about routing options with S3 hosting.

Route pages without index.html filename suffix

By default, CloudFront expects the full URL to match with the actual path in S3. For example, if you have a path <bucket_url>/<subdirectory>/index.html as below, you will need to open https://example.com/subdirectory/index.html in your browser to open the file.

AWS S3 FILE NAVIGATION

If we try to open the URL without index.html suffix, we will get AccessDenied error like below.

But there is a small trick that we can apply to open just https://example.com/subdirectory from your browser and let CloudFront/S3 serve the index.html automatically.

From an SEO perspective, it is also good to strip out the HTML suffix anyway, because the overall URL length is smaller which search engines like.

So here is the trick, the Origin Domain Name has to be updated in CloudFront in the following format:

CLOUDFRONT DISTRIBUTION -> ORIGIN AND ORIGIN GROUPS

Click ‘Edit’:

EDIT ORIGIN FORM

Change the Origin Domain Name from bucket.s3.amazonaws.com to bucket.s3-website-us-east-1.amazonaws.com (based on your region name)

Once the CloudFront Distribution is updated after the above settings change, try opening the subdirectory URL directly and it will work:

Route pages without *.html filename suffix

It is also possible to route a file such as <bucket_path>/page.html to an URL example.com/page. Yet again, this is beneficial for SEO purposes and your URL looks neat.

To get this working, follow these two steps:

  1. Remove .html suffix/extension from your original file either before uploading to S3 or after.
  2. Override the metadata Content-type of this file to text/html. Select the file -> Actions -> Change Metadata. (The default value for files without an extension is binary/octet-stream which triggers a download when opened from the browser)

Now you should be able to access your URL without the HTML suffix:

If you have more than one file to update the metadata or if you are deploying from Bitbucket Pipelines or Github Actions, you can automate this update on Metadata. Example step:

    - step:
        name: Update Metadata on HTML files
        image: fuinorg/atlassian-default-image-awscli:latest
        script:
          - >-
            while read file; do
              AWS_ACCESS_KEY_ID=$AWS_Access_key_ID AWS_SECRET_ACCESS_KEY=$AWS_Secret_access_key AWS_DEFAULT_REGION=us-east-1 aws s3 cp --content-type="text/html" --metadata-directive="REPLACE" --acl=public-read s3://<bucket>/$file s3://jiga.com.au/$file
            done <$BITBUCKET_CLONE_DIR/files_to_update_in_s3.txt

files_to_update_in_s3.txt should contain the list of files that needs to be metadata set. You can generate this file dynamically locally using gulp.

The doctype has to be set correctly in the HTML file for this to work. Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
Hello world
</body>
</html>

Note there is a little difference between the previous subdirectory approach and this one. We will have a trailing slash in the earlier approach and no slash in this one.