Author Archives: Febronei

8 Important Ways to secure DigitalOcean Droplets


What is DigitalOcean Droplets?

DigitalOcean Droplets are Linux-based virtual machines (VMs) that run on top of virtualized hardware. These droplets are new virtual servers. These virtual servers must be configured such that it provides security and usability for the application.

Basic security measures are always important as a startup of any project, and over time it is beneficial to develop a more tailored security approach which suits the specific needs of your environments and applications. Here are basic security measure you can take care of when you do initial setup and deployment process for DigitalOcean droplet.

These concepts are complex and advance topics, hence this tutorial will not cover everything regarding DigitalOcean Droplets configuration for each methods.

Let’s find more…

Step1: Use SSH KEYS

Secure Shell (SSH) is popular encrypted protocol used to communicate with servers. As a server side admin, you’ll likely SSH into your servers because it is more secure way to configure servers and databases.

With SSH keys, a private and public key pairs are created for the purpose of authentication. The private key is kept secret and secure by the user, while the public key can be shared.

When interacting with Digital Ocean plate form, You have two options either SSH keys or password

SSH key is More Secure Way

Reasons: SSH Keys are larger bit length.

Passwords are typically 8-16 characters which is easy to break.

SSH keys are 4096 bit characters long which is difficult to crack.

SSH keys are completely randomized.

SSH keys are large number of prime numbers thus it cannot be easily social engineered.

To get SSH key of someone, literally you need to access his physical computer.

Connecting to Server Using SSH Keys

To configure SSH key authentication, you must place your public SSH key on the server in its proper directory. When your client first connects to the server, the server will ask for proof that you have the associated private key. It generates a random value and send it to your SSH client. Your SSH client will then use your private key to encrypt the response and then send the encrypted reply to the server. The server then decrypts your client’s reply using your public key. If the server can decrypt the random value, then it means that your client possesses the private key and the server will let you connect without a password.

ssh-keygen

By default your key pair is saved is in ~/.ssh/ on Linux and /Users/your_username/.ssh on Windows and macOS. Simply copy your public key, which is named id_rsa.pub by default.

From the Account section, in the Security tab, find the SSH keys section. Click Add SSH Key to open the New SSH key window. Paste your public key into the SSH key content field, give it a name, then click Add SSH Key.


Step 2: Firewalls

Firewalls have basic level protection however it is essential for securing your DigitalOcean Droplet by restricting port access.

Firewalls restrict access to selected IP addresses. This is really helpful in granting ssh and MySQL access. In addition, it also helps to prevent certain users from certain countries to get access to the droplet or website.

You can configure firewall rules such that it helps to open and close ports for outside world.

eg: HTTP request (port 80)

MySQL request(port 3306)

DigitalOcean provides you two different types of firewalls

  • Operating system specific firewall and
  • DigitalOcean firewall

Operating System Level Firewall Code

sudo ufw app list
sudo ufw allow "Nginx Full"

Firewall at the Droplet Level Setup

To do Droplet level firewall simply follow these steps.

From DigitalOcean control panel, click Create in the top right to open the create menu, then click Cloud Firewalls to open the firewall create page. Configure the cloud firewall with the following options:

  1. In Name, enter inbound-ssh-only.
  2. In Inbound Rules, leave the single default rule for SSH.

Remember security is all about securing layers. So enable both options rather than one.


Step 3: Virtual Private Clouds

Virtual private clouds has features which allow large outer casing for all droplets. It is technically not a firewall but an extra layer which help to protect servers. Rules are applied to the group at once rather than applying firewall rules to each individual droplets.

Service Auditing
sudo ss-plunt

In Outbound Rules, keep the default rules, which permit all traffic to any destination on any port.

Click Apply to Droplets, add the tag you created with the new Droplet. These tags can be useful when you create additional Droplets in future, adding the same tag to them will automatically add them to this cloud firewall as well, simplifying scaling in the future.


Step 4: Unattended Updates

This method has downside. If we let auto update on then it updates number of packages which may leads to break any library. In this case it will be nearly impossible to find which caused the issue. Therefore before doing update process, remember to know all the libraries and packages will get updated.


Step 5: Backup

It is common scenario, we always take backup regularly but forget to test it. A backup that has been never tested is a theory. Taking backup is not a disaster recovery plan. A backup you think is going to work but not when the time comes then you have two problems. Don’t trust on one entity. So take backup offsite.Doing external backup is necessity

DigitalOcean has backup and Snapshot option.

  • Backup is weekly which is large in size
  • Snapshot is kind of diff

Step 6: SSL/TLS Encryption

Data is harvested by apps, companies and platforms. These data are sold to different companies. To minimize such illegal activities we have to use some type of secure way to communicate over the internet.

SSL/TLS protocol encrypts internet traffic, making secure internet communication between the users. These encryption protocols hide raw data from any third parties.


Step 7: Isolated Execution Environment

Isolated Execution is a software reference implementation of the security through isolation concept. It is not targeted at stopping malware from running or avoiding its insertion in the user’s machine; it rather provides a contained environment where malware can run without affecting the whole system.

  • Historically this is a good execution approach
  • In reality it is better to separate each component
  • Application server should be kept separately from database server
  • For example, If something happens to application server, we still able to keep database server safe without touching it
  • Application Server: Public network
  • Database Server: Private network
  • Do not put your database publicly facing to the internet

Step 8: Service Auditing

Even with all the security best practices, a new vulnerability can cause harm to your server. That’s where regular monitoring helps.

Service auditing or security auditing is a process of tracking and reviewing activities on your server. It is important to have audit policies where it states events you want to monitor, system log, history and how frequency you want to conduct the audit.


Conclusion

Security is an important key factor. It is important to have your Droplets safe and secure from harmful hackers to avoid security breaches. So, there you go. These are 8 ways you can make your Droplet secure.

Laravel Livewire CRUD Application with JetStream and Tailwindcss

This tutorial will explain how to create simple CRUD operation in Laravel 8 using Livewire and Jetstream packages. In this project we will build a simple blog which displays posts. So lets begin…

Laravel Jetstream

Laravel Jetstream is starter kit for Laravel ecosystem which provides an elegant login, registration, email verification, two-factor authentication, session management, API via Laravel Sanctum and optional team management features.

Livewire

Livewire is a full-stack framework for Laravel based applications which helps to create dynamic interfaces simple, without leaving the comfort of Laravel.

Tailwindcss

Tailwindcss is utility CSS framework that helps to build modern websites without ever leaving your HTML. Tailwindcss will come by default when you install Laravel application.

With the help of all these powerful technologies, we will show how to add a database to your project and do all CRUD stuffs using the application. In addition, we will also show basic server-side validation when we store the data into the MySQL database.

Steps include in this Project

  • Step 1: Initial set up, Install Livewire and Jetstream
  • Step 2: Database Configuration
  • Step 3: Model Migration
  • Step 4: Create Livewire Components
  • Step 5: Prepare Blade View
  • Step 6: Create Routes
  • Step 7: Start Development Server

Initial Set Up, Install Livewire and Jetstream

Let’s install Laravel fresh application using terminal. You can use any name. We are using laravel-jetstream-blog.


composer create-project --prefer-dist laravel/laravel-jetstream-blog

Now change the directory into newly created project then install Laravel jetstream.


cd laravel-jetstream-blog

composer require laravel/jetstream

php artisan jetstream:install livewire

At this point you will achieve Laravel user authentication part with the help of Jetstream. You will see Login and Register button in your app.

Now install nmp packeges and compile all the assets. Use both the npm commands as follow.


npm install && npm run dev

Database Configuration

Next, we connect our app to the database. To connect with database we need to make some configuration on the .env file.

You can use any name for DB_DATABASE, DB_USERNAME and DB_PASSWORD. Remember to change DB_HOST and DB_PORT if you are using Docker for this project (Refer docker tutorials).


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_jetstream_blog
DB_USERNAME=sail
DB_PASSWORD=password

Model Migration

It is time to create our first model. To create a model we can simply use terminal and execute the following command. Here we are creating Post model. You can give any name for the model.


php artisan make:model Posts -m

In the Post model, we will add table values such as title, body and the slug in the $fillable array.


<?phpnamespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Posts extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'body',
				'slug'

    ];
}

Next, make our first migration to the database. When we run the migration command, new migration will be placed in your database/migrations directory. We can use any name for migration. Each migration filename contains a timestamp that allows Laravel to determine the order of the migrations:


php artisan make:migration create_posts_table

Then configure the migration table for Post, so add the table properties in the database/migrations/create_post_table.php file.


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title', 100);
            $table->string('body');
            $table->string('slug');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Create Livewire Components

At this point we can create our first Livewire components. To create our files we run this command. It will generated two files, one in Http and another in resources directories.


php artisan make:livewire post

livewire class: app/Http/Livewire/Post.php

Blade view: resources/views/livewire/post.blade.php

Now open Post class in the Livewire folder and define the post methods. Basically we will have all CRUD operations such create, update and delete posts in this file.


<?php

namespace App\Http\Livewire;

use Livewire\Component;

use App\Models\Posts;

class Post extends Component

{

public $posts, $title, $body, $slug;

public $isModalOpen = 0;

public function render()

{

$this->posts=Posts::all();

return view('livewire.post');

}

public function create(){

$this->resetForm();

$this->openModalPopover();

}

public function openModalPopover()

{

$this->isModalOpen = true;

}

public function closeModalPopover()

{

$this->isModalOpen = false;

}

public function resetForm(){

$this->title='';

$this->body='';

$this->slug='';

}

public function store(){

$this->validate([

'title' => 'required',

'body' => 'requuired',

'slug' => 'required',

]);

Post::updateOrCreate([

'id'=>$this->post_id],

[

'title'  => $this->title,

'body'   => $this->body,

'slug'   => $this->slug,

]);

session()->flash('message', $this->post_id ? 'Post updated' : 'Post created');

$this->closeModalPopover();

$this->resetForm();

}

public function edit($id) {

$post = Post::findOrFail($id);

$this->id = $id;

$this->title = $post->title;

$this->body = $post->body;

$this->slug = $post->slug;

$this->openModalPopover();

}

public function delete($id){

Post::find($id)->delete();

session()->flash('message', 'Message deleted Successfully');

}

}

Prepare Blade View

Now let’s open post blade file, so that we can display all the data in the UI.

It will show data in tabular forms where users can take actions to update or edit post data.

To achieve that, simply insert the following code in resources/views/livewire/post.blade.php file.


<x-slot name="header">
<h2 class="text-center">Laravel 8 Livewire Blog Demo</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg px-4 py-4">
@if (session()->has('message'))
<div class="bg-teal-100 border-t-4 border-teal-500 rounded-b text-teal-900 px-4 py-3 shadow-md my-3"
role="alert">
<div class="flex">
<div>
<p class="text-sm">{{ session('message') }}</p>
</div>
</div>
</div>
@endif
<button wire:click="create()"
class="bg-green-700 text-white font-bold py-2 px-4 rounded my-3">Create Student</button>
@if($isModalOpen)
@include('livewire.create')
@endif
<table class="table-fixed w-full">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-2 w-20">No.</th>
<th class="px-4 py-2">Title</th>
<th class="px-4 py-2">Body</th>
<th class="px-4 py-2">Slug</th>
<th class="px-4 py-2">Action</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td class="border px-4 py-2">{{ $post->id }}</td>
<td class="border px-4 py-2">{{ $post->title }}</td>
<td class="border px-4 py-2">{{ $post->body}}</td>
<td class="border px-4 py-2">{{ $post->slug}}</td>
<td class="border px-4 py-2">
<button wire:click="edit({{ $post->id }})"
class="bg-blue-500  text-white font-bold py-2 px-4 rounded">Edit</button>
<button wire:click="delete({{ $student->id }})"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Delete</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>

Lastly create a new file named create.blade.php file inside the resources/views/livewire/ then add the below code in that file.


<div class="fixed z-10 inset-0 overflow-y-auto ease-out duration-400">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen"></span>?
    <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
        role="dialog" aria-modal="true" aria-labelledby="modal-headline">
        <form>
            <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
                <div class="">
                    <div class="mb-4">
                        <label for="exampleFormControlInput1"
                            class="block text-gray-700 text-sm font-bold mb-2">Name</label>
                        <input type="text"
                            class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                            id="exampleFormControlInput1" placeholder="Enter Name" wire:model="name">
                        @error('name') <span class="text-red-500">{{ $message }}</span>@enderror
                    </div>
                    <div class="mb-4">
                        <label for="exampleFormControlInput2"
                            class="block text-gray-700 text-sm font-bold mb-2">Email:</label>
                        <textarea
                            class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                            id="exampleFormControlInput2" wire:model="email"
                            placeholder="Enter Email"></textarea>
                        @error('email') <span class="text-red-500">{{ $message }}</span>@enderror
                    </div>
                    <div class="mb-4">
                        <label for="exampleFormControlInput2"
                            class="block text-gray-700 text-sm font-bold mb-2">Mobile:</label>
                        <textarea
                            class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                            id="exampleFormControlInput2" wire:model="mobile"
                            placeholder="Enter Mobile"></textarea>
                        @error('mobile') <span class="text-red-500">{{ $message }}</span>@enderror
                    </div>
                </div>
            </div>
            <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
                <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
                    <button wire:click.prevent="store()" type="button"
                        class="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-red-600 text-base leading-6 font-bold text-white shadow-sm hover:bg-red-700 focus:outline-none focus:border-green-700 focus:shadow-outline-green transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                        Store
                    </button>
                </span>
                <span class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
                    <button wire:click="closeModalPopover()" type="button"
                        class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-base leading-6 font-bold text-gray-700 shadow-sm hover:text-gray-700 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                        Close
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>
</div>

Create Routes

Now it is time to create our first route. It enables navigation for the Livewire app. Remember to import all Livewire classes. In this case Post class and define the route method. To do so, open resources/web.php file.


<?php

use Illuminate\Support\Facades\Route;
use App\Http\Livewire\Post;

Route::get('posts', Post::class);

Start Development Server

Now run the development server. So, use php artisan command along with the serve tag to invoke the Laravel development server.


php artisan serve

You can test the app by using the given url on the browser’s address bar.

http://127.0.0.1:8000/posts

Thats all!

Solving On the Fly Route with Named Route in Flutter


Overview


In typical scenarios, we need to create a constructor if we want to send data from one widget or one screen to another. Let’s say we are building an e-commerce app and we have the following screens in our application.

  • main.dart  (Main Screen)
  • products_screen.dart (Products Screen)
  • product_item_screen.dart (Product Item Screen)
  • product_details_screen.dart (Product Detail Screen)

Let’s dive deep…

Assume we have few images on the Grid Tile in Products Screen. When we click one of the image, users will be sent to Product Detail Screen. When we want to send data to Product Detail Screen, the typical method is to create a constructor and then pass the data.


child: GestureDetector(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (ctx) => ProductDetailScreen(title, price),
              ),
            );
          },
          child: Image.network(
            imageUrl,
            fit: BoxFit.cover,
          ),
        ),

This method is called on the fly route. This method is perfectly fine but it has some downside.


Named Route

When the application grows, as requirements increases, we need to create many such on the fly route which is difficult to maintain. Specially new developers may face difficulty to understand which routes and screens the app has.


To solve this problem we have to use Named route in Main Screen.  It will be easier to find all the routes and screens that the application has.


Another problem of having on the fly route is often unnecessary data has to be passed through multiple pages or screens. We have different navigations within multiple screens and we sometime want to pass data within these screens. In this case we have to pass unnecessary data in every widget down in the tree. The problem is, sometime all the screens not necessarily need those data in the widget itself to display data. These screens simply want to forward data to another widget. To understand the concept better see the following example below.


Let’s say we want to add price field in the Product Detail Screen. So we create a constructor.


import 'package:flutter/material.dart';

// Product Detail Screen
class ProductDetailScreen extends StatelessWidget {
  
  final String title;
  final double price;

  ProductDetailScreen(this.price, this.price);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text(price),
      ),
    );
  }
}

We make a GestureDetector widget on Product Item Screen and make the image clickable. When we click the image, user will then be routed to Product Detail Screen. Value for price will be passed into the ProductDetailScreen as shown.


// Product Item Screen
child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (ctx) => ProductDetailScreen(title, price),
              ),
            );
          },
          child: Image.network(
            imageUrl,
            fit: BoxFit.cover,
          ),
        ),
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          title: Text(
            title,
            textAlign: TextAlign.center,
          ),
        ),
      ),

Remember we don’t directly display the price value in Product Item Screen. We only need to pass the value to Product Detail Screen. Because of this method unnecessary data will be used in different pages or widgets.


Unnecessary rebuilds of the major parts of the widgets causes performance issues. So no need to rebuild the entire app when just simple tiny widget needs an update.

Therefore we need a better approach. This is where Named route and State management come into play.


How to Use Named Route?

To make Named routes, first create a route table in the Main Screen. Register route name and then import corresponding files.


import 'package:flutter/material.dart';

import './screens/product_detail_screen.dart';
import './screens/products_overview_screen.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shop App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ProductOverviewScreen(),
      routes: {
        ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
      },
    );
  }
}

Use pushNamed route in the Product Item Screen. Then use id as argument. This value is important because, it will be used to retrieve data in the Product Detail Screen. You may have noticed, we are passing only id value. But in the previous method (On the fly route method, we need to pass all the required field as argument).


class ProductItem extends StatelessWidget {
  final String id;
  final String title;
  final double price;
  final String imageUrl;

  ProductItem(
    this.id,
    this.title,
    this.price,
    this.imageUrl,
  );

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              ProductDetailScreen.routeName,
              arguments: id,
            );
          },
          child: Image.network(
            imageUrl,
            fit: BoxFit.cover,
          ),
        ),

Now simply remove the constructor in the Product Detail Screen that we created earlier and then use route path. Then extract id value so that we can use all the required data for each corresponding id.


import 'package:flutter/material.dart';

class ProductDetailScreen extends StatelessWidget {
  static const routeName = '/product-detail';

  @override
  Widget build(BuildContext context) {

    final productId = ModalRoute.of(context).settings.arguments as String;
    return Scaffold(
      appBar: AppBar(
        title: Text('title'),
      ),
      body: Center(
        child: Text('price'),
      ),
    );
  }
}

State Management

From official Flutter doc

When we add Data provider in MyApp widget, all child widgets will be able to listen to that provider. For that we need to listen by placing a listener on any widget we want to listen. By doing so, only that widget gets rebuild as data get update.

Install provider package and add in the pubspec.yaml file. Now we are able to use provider package which give access to provider and state management.


dependencies:
  flutter:
    sdk: flutter
  provider: ^4.0.5+1

Now create products provider. This is just dummy data but, you can use your own data.


class Products with ChangeNotifier {
  List<Product> _items = [
    Product(
      id: 'p1',
      title: 'Red Shirt',
      description: 'A red Shirt- Bitch',
      price: 34.99,
      imageUrl:
                   'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
    ),
    Product(
      id: 'p4',
      title: 'Pad',
      description: 'A red Pad',
      price: 19.99,
      imageUrl:
          'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
    ),
  ];

  List<Product> get items {
    return [..._items];
  }

  void addProduct() {
    //_items.add(value);
    notifyListeners();
  }
}

Now we start listening by providing in different widgets of our app. Simply import it and provide it at the highest possible point of the widgets. In this case, we provide it is in MyApp widgets.


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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (ctx) => Products(),
        ),
      ],
      child: MaterialApp(
        title: 'Shop App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ProductOverviewScreen(),
        routes: {
          ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
        },
      ),
    );
  }
}

Here we are using ProductsGrid class simply to make 2 column grid for the products.

class ProductsGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final productsData = Provider.of<Products>(context);
    final products = productsData.items;

    return GridView.builder(
      padding: const EdgeInsets.all(10.0),
      itemCount: products.length,
      itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
        value: products[i],
        child: ProductItem(),
      ),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 3 / 2,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
      ),
    );
  }
}

Now create ProductItem class which use Product provider. When we click the GestureDetector button, the ProductDetailScreen will show up.

class ProductItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final product = Provider.of<Product>(context, listen: false);
    // final cart = Provider.of<Cart>(context);
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              ProductDetailScreen.routeName,
              arguments: product.id,
            );
          },
          child: Image.network(
            product.imageUrl,
            fit: BoxFit.cover,
          ),
        ),
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          leading: Consumer<Product>(
            builder: (ctx, product, child) => IconButton(
              icon: Icon(
                  product.isFavorite ? Icons.favorite : Icons.favorite_border),
              onPressed: () {
                product.toggleFavoriteStatus();
              },
            ),
          ),
          title: Text(
            product.title,
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );
  }
}

Here is code for ProductDetailScreen. In this class we have to use Products model so that we can use each product properties such as name, title and description.

class ProductDetailScreen extends StatelessWidget {
  static const routeName = '/product-detail';

  @override
  Widget build(BuildContext context) {
    final productId = ModalRoute.of(context).settings.arguments as String;
    final loadedProduct = Provider.of<Products>(context).items.firstWhere(
          (prod) => prod.id == productId,
        );

    return Scaffold(
      appBar: AppBar(
        title: Text(loadedProduct.title),
      ),
      body: Center(
        child: Text(loadedProduct.description),
      ),
    );
  }
}

Conclusion

There you go. Now you know how to create named routes in flutter. This is just small app with just only few screens. But named routes could be more beneficial in large apps with multiple screens. So hope you understand something from this tutorial.

Connect Firebase with Flutter for Android and iOS Apps


Overview


Firebase is simple but great backend for flutter apps. Firebase is now a product of Google which comes with countless of features such as authentication, firestore and real time database. Dart and flutter framework is also from Google hence it has official support for Firebase with FluterFire set of libraries.

The initial setup of flutter app integration with Firebase is simple and seamless. In this tutorial we will learn how to integrate Firebase backend with a flutter app.

So let’s begin…

To begin we need to create a flutter app.  To create a flutter app simply enter the following command in VS code terminal


#flutter create <appName>

flutter create flutterApp 

Now it is time to do the configuration for Firebase and connect the app with Firebase backend.


Step1: Registration app


Go to Firebase console.

https://firebase.google.com/


In Firebase dashboard, select Create new project and give a name for your project.

Add project name

Firebase asks for analytics. For this project we will not enable Google analytics. Now select Create Project


Disable Google analytics and continue

Firebase will continue the process in the background and make the project ready for us.


Firebase will run background to make our project ready

Click Continue and then go back to Firebase dashboard. Select any app either iOS or Android.  In this case, we will start with android app and will go for iOS later.


Firebase dashboard

Now go to your project in VS code. Navigate to android/app/build.gradle. You can see the applicationId similar as com.example.firebaselogin.

You can also find the name in AndroidManifest.xml file in android folder.

Android package name and the applicationId must be same.  Simply add same name for your project. It is very important. Remember that! We will leave app nickname as blank for simplicity then Register app.


Register app

Step 2: Downloading config file


Now download config file and then store it in the flutter app. The location is important as it has API keys and other important information for Firebase to use. Location: ~flutter app/android/app folder.


Download json file

Now it is time to add Firebase SDK. Modify build.gradle file to add classpath.  Remember to open project-level build gradle (<project>/build.gradle)


Add dependencies

Step 3: Modify Gradle file


Add all plugins and click Next

Now we have successfully implemented Google service plugin in our project. We need to uninstall the app and build it again to run the app on a simulator or on a mobile device.


Go to console

Ok! Now it’s time to add iOS app


iOS has similar step. Open the iOS project in Xcode at ios/Runner/Runner.xcodeproj and copy the Bundle identifier under General:


Runner file

Select iOS platform from Firebase dashboard.  You will see a similar screen where we add an iOS Bundle ID. By default both android and iOS bundle name will be same. It is better to keep same name for consistency: leave all the optional fields blank and click Register app to move next step.


Register iOS app

Download GoogleService-Info.plist and drag this file into the root of your Xcode project within Runner: Again remember path is important.


Download plist file

It’s important to use Xcode to put the GoogleService-info.plist file, as this will not work otherwise.


Use Xcode to move plist file

Click Next

Click Next

Click Continue to console

Conclusion


We’ve learned how to hook a flutter application with Firebase backend. We have created both android and iOS app on Firebase backend and then configured to connect with our application by downloading GoogleService-Info.plist file.

Learn Everything About Feature Scaling


Feature Scaling?


Feature scaling is a technique used when we create a machine learning model. It lets you to normalize the range of independent variables or features of the given field of the dataset. It is also known as data normalization. During data preprocessing phase, it is important to do data normalization because, machine learning algorithm will not perform well if the data attributes have different scales.

let’s scratch the surface…

Why Feature Scaling is Important?


The importance of feature scaling is can be illustrated by the following simple example.

Suppose in a dataset we have features and  each feature has different records.


featuresf1f2f3f4f5
Magnitude3004001520550
UnitKgKgcmcmg

Remember every feature has two components


  • Magnitude  (Example: 300)
  • Unit (Example: Kg)

Always keep in mind: Most of the ML algorithms work based on Euclidean distance, Manhattan distance or K Nearest-Neighbors and few others.


featuresf1f2f3f4f5 (f2- f1) (f4- f3)
Magnitude3004001520550400-300 = 10020-15=5
UnitKgKgcmcmgKgcm

So coming back to this example, so when we try to find out the distance between different features, the gap between them actually varies. Some attributes have large gap in between while others are very close to each other. See the table:

You may also have noticed, unit of f5 is in gram(g) while f1 and f2 are in Kilo gram (Kg). So in this case, the model may consider the value of f5 is greater than f1 and f2 but that’s not the case. Because of these reasons, the model may give a wrong predictions.

Therefore we need to make all the attributes (f1, f2, f3…) to have same scale with respect to its units.  In short, we need to convert all the data into same range (usually between 0-1)  such that no particular feature gets dominant over another or no particular feature has less dominant. (By doing so, the convergence will be also much fast and efficient).

There are two common methods used to get all attribute into same scale.


Min-max Scaling


In min-max scaling, values are rescaled to a range between 0 to 1. To find the new value,  we need to subtracting the min value and then divide by the max minus the min. Scikit-Learn provides MinMaxScaler for this calculation.



    \[X_{new} = \frac{ Xi-min(X)}{max(X)-min(X)}\]

Standardization

Standardization is much less affective by outliers (explain outliers – link) . First we need subtract the mean value then divide by standard deviation such that it forms resulting distribution of unit variance. Scikit-Learn provides  a transformer called StandardScaler for this calculation.



    \[ X_{new} = \frac{Xi-X_{mean}}{Standard Deviation} \]


Here I show an example for feature scaling using min-max scaling and standardization. I’m using google colab but you can use any notebook/Ide such as Jupyter notebook or PyCharm.


Go to the link and download Data_for_Feature_Scaling.csv


Upload csv to the google drive

Mount drive to the working notebook

For that you may need authorization code from google Run the code.


# feature scaling sample code
# import recommended libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import preprocessing
# mount drive
from google.colab import drive
drive.mount('/content/drive')
# import dataset 
data_set = pd.read_csv('feature_scaling/Data_for_Feature_Scaling.csv')
# check the data 
data_set.head()

Output
        Country	 Age	Salary	Purchased
0	France	 44	72000	0
1	Spain	 27	48000	1
2	Germany	 30	23000	0
3	Spain	 38	51000	0
4	Germany	 40	1000	1

x = data_set.iloc[:, 1:3].values
print('Origianl data values: \n', x)

Output
Original data values: 
 [[  44   72000]
 [   27   48000]
 [   30   23000]
 [   38   51000]
 [   40    1000]
 [   35   49000]
 [   78   23000]
 [   48   89400]
 [   50   78000]
 [   37   9000]]

from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
# Scaled feature 
x_after_min_max_scaler = min_max_scaler.fit_transform(x)
print('\n After min max scaling\n', x_after_min_max_scaler)

Output
After min max scaling
 [[0.33333333  0.80316742]
 [0.           0.53167421]
 [0.05882353   0.24886878]
 [0.21568627   0.56561086]
 [0.25490196   0.        ]
 [0.15686275   0.54298643]
 [1.           0.24886878]
 [0.41176471   1.        ]
 [0.45098039   0.87104072]
 [0.19607843   0.09049774]]

# Now use Standardisation method
Standardisation = preprocessing.StandardScaler()
x_after_Standardisation = Standardisation.fit_transform(x)
print('\n After Standardisation: \n', x_after_Standardisation)

Output
After Standardisation: 
 [[ 0.09536935  0.97512896]
 [-1.15176827   0.12903008]
 [-0.93168516  -0.75232292]
 [-0.34479687   0.23479244]
 [-0.1980748   -1.52791356]
 [-0.56487998   0.1642842 ]
 [ 2.58964459  -0.75232292]
 [ 0.38881349   1.58855065]
 [ 0.53553557   1.18665368]
 [-0.41815791  -1.2458806 ]]

Learning resources:

Import CSV file to Firestore using GCP

Overview

If you have large records of data, entering each record into cloud Firestore database manually is time consuming. Not to mention that it is going to be a tedious as well. In this tutorial you will learn an easy method to import large CSV file to Google Cloud Firestore using GCP and Node.js. Therefore you need Google account and valid CSV format file.  Before that, let’s know what GCP and Cloud Firestore is all about.

let’s start…

GCP

Google provide seamless cloud computing services to various clients across the world using Google Cloud Platform (GCP). They provide series of modular services such as hosting services, data storage, data analytics and Machine Learning (ML) and many more which use Google Hardware infrastructure to run.

Cloud Firestore

When you look for a fast NoSQL document database, then there is nothing better than Cloud Firestore provided by Google. It is serverless, meaning it simplifies data storing, syncing, and data querying for developers. It also supports real time synchronization and offline support.  Security is another feature where developers spend less time on security of the application, therefore time requires to develop a mobile application is significantly reduced.

GCP setup

Create new project in Google Cloud Platform (GCP). In this case concilsVote (you can give a name as your wish). This is how the main menu looks like. Click Firestore and then Data.

Then create new project by clicking NEW PROJECT or CREATE PROJECT as shown. If you have existing project you may search the project and then use it.

Fill the form and enter CREATE. (You need to enter suitable project name and Location).

Now, select the project you just created from the drop down and then click OPEN.

You will see the following screen. Choose SELECT NATIVE MODE from the options given.

Then choose, the nearest location where to store the data and CREATE DATABASE. Please select the location carefully because we are unable to change the location once it’s being created.

You will see process of database creation process running in the background.

Now you will see a screen like this. It says Your database is ready to go. Just add data. It is time to Activate Cloud Shell.

Enter the following command below to check whether the project is configured or not.

gcloud config list project

Now set the project ID using the below command. You may see the project ID from the list.

gcloud config set project PROEJCTID
gcloud config set project concilsVote

Now write some logic to read CSV data and import it to Firestore.

Create a new directory named concilsVote in the terminal and then change the directory.

mkdir concilsVote
cd concilsVote

Initialize npm using the below command in the terminal and fill the details.

npm init

Press Yes, and enter for all the options, until package.json file is get created with the details below.

{
"name": "rainfallcsvexport",
"version": "1.0.0",
"description": "Rainfall Data conversion",
"main": "index.js",
"scripts": { "test": "echo "Error: no test specified" && exit 1" },
"author": "Your name",
"license": "ISC"
}

Now you need to install the following dependencies. Just run the following npm commands.

npm install @google-cloud/firestore
npm install csv-parse

Create a new file named concilVote.js using the command in the terminal (Any name is fine). Copy the below code and paste in the file.

const {readFile}  = require('fs').promises;
const {promisify} = require('util');
const parse       = promisify(require('csv-parse'));
const {Firestore} = require('@google-cloud/firestore');
if (process.argv.length < 3) {
  console.error('Please include a path to a csv file');
  process.exit(1);
}
const db = new Firestore();
function writeToFirestore(records) {
  const batchCommits = [];
  let batch = db.batch();
  records.forEach((record, i) => {
    var docRef = db.collection('rainfall').doc(record.SUBDIVISION);
    batch.set(docRef, record);
    if ((i + 1) % 500 === 0) {
      console.log(`Writing record ${i + 1}`);
      batchCommits.push(batch.commit());
      batch = db.batch();
    }
  });
  batchCommits.push(batch.commit());
  return Promise.all(batchCommits);
}
async function importCsv(csvFileName) {
  const fileContents = await readFile(csvFileName, 'utf8');
  const records = await parse(fileContents, { columns: true });
  try {
    await writeToFirestore(records);
  }
  catch (e) {
    console.error(e);
    process.exit(1);
  }
  console.log(`Wrote ${records.length} records`);
}
importCsv(process.argv[2]).catch(e => console.error(e));

Now we will upload the CSV file. Upload the CSV file to the concilVote folder by right-clicking the folder name.

To import data to Firestore simply run the following command.

node concilVote.js concilVote.csv

Click Enter, and now you are able to see the data getting imported. If everything goes well, you will see a message like below, in the console. Now just refresh the Firestore console page and you will see the data.

Wrote 5302 records

Congratulations