DEV Community

Cover image for Wtf is Grpc? Part 2: Custom Notification(without Firebase) in Flutter and Golang.
Md. Mobin
Md. Mobin

Posted on

5 1 1

Wtf is Grpc? Part 2: Custom Notification(without Firebase) in Flutter and Golang.

Welcome back, intrepid tech explorers! If you survived the roller-coaster of gRPC, Flutter, and Golang from our last adventure, congratulations! You're now ready for Part 2: "Wtf is gRPC? Part 2: Custom Notification (without Firebase) in Flutter and Golang." 🎉

In this next thrilling installment, we'll tackle custom notifications without the Firebase safety net. Get ready to dive into the world of Flutter and Golang, where we'll craft notifications that are as unique as your favorite coding quirks. So, fasten your seatbelts and keep your code close; it's going to be a wild ride through the land of tech enchantment! 🚀💻✨

Meme 1

Table of Contents: Navigating the Streaming Circus 🎪

The Streaming Showdown: gRPC Unleashed

In simple words A server-streaming RPC is similar to a unary RPC, except that the server returns a stream of messages in response to a client’s request. After sending all its messages, the server’s status details (status code and optional status message) and optional trailing metadata are sent to the client. This completes processing on the server side. The client completes once it has all the server’s messages.

Unary vs. Server Streaming: The Tech Battle of the Century

In this epic tech showdown, I am going to compare Unary Streaming and Server Streaming, with a touch of TV series for better understanding.

Aspect Unary Streaming Server Streaming TV Series Comparison
Data Transfer Single requests Continuous data stream The Office (Episode-by-episode)
Communication Overhead Less Slightly more Friends (Constant chatter)
Real-time Updates Not suitable Ideal for real-time Stranger Things (Always suspenseful)
Complexity Simpler More complex Black Mirror (Mind-bending)
Scalability Limited by requests Highly scalable Game of Thrones (Vast world)
Use Cases Simple operations Real-time data streaming Breaking Bad (Intense)
Resource Consumption Lower Higher The Mandalorian (High production)
Parallel Processing Limited Effective The Big Bang Theory (Many characters)
Error Handling Simple Handling errors in streams The Twilight Zone (Mysterious)
Performance Suitable for small-scale Suitable for large-scale The Walking Dead (Epic)
Winner Suitable for specific tasks Ideal for real-time data Server Streaming Showdown!

grpc-server-side

Let's Play with Data: The Streaming Playground

Let's play with streaming! Imagine that the client is a stand-up comedian whose job is to tell jokes, and the server, representing the audience, responds with laughter until they've had enough."

  • Let's create required message and service.

joke.proto



syntax = "proto3";

package pb;
option go_package="github.com/djsmk123/server/pb";
//Response send by standup comedian to audience 
message Joke{
    string joke=1;
}
// Response Recieved by standup comedian from audience
message JokeResponse{
    string laugh_intensity=1;
};


Enter fullscreen mode Exit fullscreen mode

rpc_services.proto:



syntax="proto3";
package pb;
option go_package="github.com/djsmk123/server/pb";
import "joke.proto";
service GrpcServerService {
    rpc ThrowAJoke(Joke) returns(stream JokeResponse){};
}


Enter fullscreen mode Exit fullscreen mode
  • Implement ThrowAJoke() function in golang.


func (s *Server) ThrowAJoke(joke *pb.Joke, stream pb.GrpcServerService_ThrowAJokeServer) error {
    for i := 1; i <= 10; i++ {
        response := &pb.JokeResponse{
            LaughIntensity: fmt.Sprintf("LOL %d", i),
        }
        if err := stream.Send(response); err != nil {
            return err
        }
        time.Sleep(1 * time.Second)
    }
    return nil
}


Enter fullscreen mode Exit fullscreen mode
  • Let's call service now.

grpc-streaming-output

meme1


Setting Up the Golang Server: Where Code Meets Magic

We are going to create a notification service that will listen to a notification-gRPC streaming service in the background. If any new user is added to the MongoDB collection named users, we will send data to the listener, which will then display notifications in the application.

notification-service

  • Let's continue code from part 1,where we left.

git clone git@github.com:Djsmk123/Wtf-is-grpc.git

  • Create a new message for the notification response that has to be returned by the server.

rpc_notification.proto



syntax="proto3";
package pb;

option go_package="github.com/djsmk123/server/pb";


message NotificationMessage{
    int32 id=1;
    string title=2;
    string description=3;
}


Enter fullscreen mode Exit fullscreen mode
  • Create new rpc in rpc_services.proto. ```

//keep everything same
//import NotificationMessage
import "rpc_notifications.proto";
//same
service GrpcServerService {
//keep same
// create new rpc function here
rpc GetNotifications(EmptyRequest) returns (stream NotificationMessage){}
}

- Create equivalent code for golang from proto.
Enter fullscreen mode Exit fullscreen mode

protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
proto/*.proto


- Create new package called `services` in root folder(`server`).

> We are using **goroutines** and **channels** to listen for changes in the database. If you want to learn more about goroutines, you can read this [blog](https://betterprogramming.pub/golang-how-to-implement-concurrency-with-goroutines-channels-2b78b8077984)

- Create new services `notification.go`.
package services
import (
"context"
"fmt"
"log"
"math/rand"
"time"
"github.com/djsmk123/server/db/model"
"github.com/djsmk123/server/pb"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
// NotificationNewUser is a function that listens for new user additions in a MongoDB collection and sends notifications.
func NotificationNewUser(collection *mongo.Collection, notificationCh chan *pb.NotificationMessage) {
// Create a context with a timeout
ctx := context.Background()
//defer cancel() // Deferred cancel() call, if needed.
fmt.Println("Notification service called")
// Define a pipeline to watch for changes (insertions)
pipeline := []bson.M{
{
"$match": bson.M{
"operationType": "insert", // Watch for insertions
"fullDocument.name": bson.M{
"$exists": true, // Ensure the 'name' field exists
},
},
},
}
// Create a change stream
changeStream, err := collection.Watch(ctx, pipeline)
if err != nil {
fmt.Println("Error creating change stream error:", err)
return
}
fmt.Println("Listening for new user additions...")
// Start a goroutine to handle changes from the stream
for changeStream.Next(ctx) {
var changeDocument bson.M
if err := changeStream.Decode(&changeDocument); err != nil {
log.Println("Error decoding change document:", err)
continue
}
fullDocumentJSON, err := bson.MarshalExtJSON(changeDocument["fullDocument"], false, false)
if err != nil {
log.Println("Error converting fullDocument to JSON:", err)
continue
}
// Extract the updated user name from the change document
var user model.UserModel
fmt.Println("user:", string(fullDocumentJSON))
if err := bson.UnmarshalExtJSON(fullDocumentJSON, false, &user); err != nil {
log.Println("Error unmarshaling user:", err)
continue
}
rand.Seed(time.Now().UnixNano())
// Generate a random int32
randomInt := rand.Int31()
// Create a new user notification
newUserNotification := &pb.NotificationMessage{
Title: "A new family member",
Description: user.Name + " just arrived, send 'hi' message to connect.",
Id: int32(randomInt),
}
// Send the notification to the channel
notificationCh <- newUserNotification
}
if err := changeStream.Err(); err != nil {
log.Println("Error in change stream:", err)
}
}
view raw notification.go hosted with ❤ by GitHub
  • Now just have to define rpc GetNotification in gapi. so create a new file notification.go in package gapi.

    package gapi
    import (
    "fmt"
    "github.com/djsmk123/server/pb"
    "github.com/djsmk123/server/services"
    )
    // GetNotifications is a gRPC service method that streams notifications to the client.
    func (server *Server) GetNotifications(req *pb.EmptyRequest, stream pb.GrpcServerService_GetNotificationsServer) error {
    fmt.Println("Notification service started")
    // Create a channel for receiving notifications
    notificationCh := make(chan *pb.NotificationMessage)
    // Start the background service to listen for new user additions
    go services.NotificationNewUser(server.dbCollection.Users, notificationCh)
    fmt.Println("Notification service created")
    // Continuously listen for events
    for {
    select {
    case <-stream.Context().Done():
    // Client disconnected, close the channel and exit
    close(notificationCh)
    return nil
    case notification, ok := <-notificationCh:
    if !ok {
    // The channel has been closed, exit the loop
    return nil
    }
    // Send the received notification to the client
    if err := stream.Send(&pb.NotificationMessage{
    Title: notification.Title,
    Id: int32(notification.Id),
    Description: notification.Description,
    }); err != nil {
    return err
    }
    }
    }
    }
    view raw notification.go hosted with ❤ by GitHub
  • Wait, we can't listen change stream in mongodb without converting standalone to replica set more info here.

  • In terminal open path (windows)

cd C:\Program Files\MongoDB\Server\&lt;mongo-version&gt;\bin

  • Execute this command
Enter fullscreen mode Exit fullscreen mode

mongod --port 27017 --dbpath="C:\Program Files\MongoDB\Server<mongo-version>\data" --replSet rs0 --bind_ip localhost

- Re-run mongo service

`rs.initiate()`

- Run server and let's test in `evans-cli`.

## Fluttering into Action: Your Magical Streaming App - Add following dependencies to flutter project
Enter fullscreen mode Exit fullscreen mode

dependencies:
flutter_background_service: ^5.0.1
flutter_local_notifications: ^15.1.1
flutter_background_service_android: ^6.0.1


- Create equivalent code for dart 

Enter fullscreen mode Exit fullscreen mode

protoc --proto_path=proto --dart_out=grpc:lib/pb proto/*.proto

- Create new services,called `notification.dart`

// ignore_for_file: depend_on_referenced_packages
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_app/pb/empty_request.pb.dart';
import 'package:flutter_app/pb/rpc_notifications.pb.dart';
import 'package:flutter_app/services/grpc_services.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
class NotificationServices {
static String notificationChannelId = 'user-notification-channel';
static int notificationId = 888;
static String notificationService = "notification-service";
// Initialize the background notification service.
static Future<void> initializeService() async {
final service = FlutterBackgroundService();
AndroidNotificationChannel channel = AndroidNotificationChannel(
notificationChannelId, // id
notificationService,
description:
'This channel is used for important notifications.', // description
importance: Importance.high,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
if (Platform.isIOS || Platform.isAndroid) {
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
iOS: DarwinInitializationSettings(),
android: AndroidInitializationSettings('ic_bg_service_small'),
),
);
}
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart, // Function to execute when service starts.
autoStart: true, // Automatically start the service.
isForegroundMode: false,
notificationChannelId:
notificationChannelId, // Notification channel ID to use.
autoStartOnBoot: true, // Automatically start on device boot.
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: onIosBackground,
),
);
service.startService();
}
// Function to execute when the service is running in the background on iOS.
static Future<bool> onIosBackground(ServiceInstance service) async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.reload();
final log = preferences.getStringList('log') ?? <String>[];
log.add(DateTime.now().toIso8601String());
await preferences.setStringList('log', log);
return true;
}
// Function to execute when the service starts (in the background or foreground).
static Future<void> onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
if (service is AndroidServiceInstance) {
service.on('setAsForeground').listen((event) {
service.setAsForegroundService();
});
service.on('setAsBackground').listen((event) {
service.setAsBackgroundService();
});
}
service.on("stop_service").listen((event) async {
await service.stopSelf();
});
if (service is AndroidServiceInstance) {
final res = getNotification();
res.listen((event) {
flutterLocalNotificationsPlugin.show(
notificationId,
event.title,
event.description,
NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId, notificationService,
icon: 'ic_bg_service_small', ongoing: false, autoCancel: true),
),
);
});
}
}
// Stream that receives NotificationMessage from the gRPC service.
static Stream<NotificationMessage> getNotification() async* {
final request = EmptyRequest();
final responseStream = GrpcService.client.getNotifications(request);
await for (var notification in responseStream) {
// Yield each received NotificationMessage.
yield notification;
}
}
}
view raw notification.dart hosted with ❤ by GitHub
  • Call notification service function in splash_screen.dart
Enter fullscreen mode Exit fullscreen mode

Future initAsync() async {
try {
await NotificationServices.initializeService();
//keep same
} catch (e) {
log(e.toString());
navigateToLogin();
}
}


- Let's build application and listen for new user.

![flutter notification without firebase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osm2tlvnlp35mxhj2f1v.jpg)

Thank you for joining us on this journey so far! We've covered a lot in this series, from setting up the server-side of our application in Golang, handling notifications, to building a foundation for communication.

In the next and final part of this series, we'll explore one of the most exciting features of gRPC: bi-directional streaming. We'll be implementing a real-time chat service using Flutter and Golang. Get ready for a dynamic and interactive experience that showcases the power and flexibility of gRPC.

So, stay tuned and keep coding! We can't wait to see you in the last part of this series where we'll bring it all together and create a fully functional chat application.

If you have any questions or feedback, please feel free to reach out. Happy coding, and see you in the next installment!



## Source code : 
[Github Repo](https://github.com/Djsmk123/wtf-is-grpc/tree/part2)

## Follow me on 

- [Twitter](https://twitter.com/smk_winner)

- [Instagram](https://www.instagram.com/smkwinner/)

- [Github](https://www.github.com/djsmk123)

- [linkedin](https://www.linkedin.com/in/md-mobin-bb928820b/)

- [dev.to](https://dev.to/djsmk123)

- [Medium](https://medium.com/@djsmk123)




Enter fullscreen mode Exit fullscreen mode

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (2)

Collapse
 
amirgm profile image
Amir Golmoradi •

Hello! I'm new to the DEV community, but I saw your post about Golang. I'm wondering which one is better for development and getting a job in 2023 ! Java Or Golang . In my country, Java has a 4x bigger community than Go, so I'm not sure which one to choose.
Thanks For your Time :D

Collapse
 
djsmk123 profile image
Md. Mobin •

Always go there where you have more opportunity so go for java. Once you learn Java I don't think it will much hard to switch to other like golang or other.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

đź‘‹ Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay