Go ServerLess with Firebase cloud functions

Firebase Cloud function

With announcement of cloud functions beta at google cloud next 2017 event, google has added one of the highly requested features in the firebase suite. This is one major step from google in making firebase serverless. In this post we will see some of the capabilities, pros and cons, setup and deployment of firebase cloud functions. Google IO is just days away, knowing about firebase is surely going to help in understanding upcoming firebase features.

Serverless computing, also known as function as a service (FaaS), is a cloud computing code execution model in which the cloud provider fully manages starting and stopping of a function’s container as necessary to serve request. Rather than per virtual machine, requests are billed by an abstract measure of resources required to satisfy the request. The name serverless architecture / computing does not mean no servers are involved, it means the maintenance, scaling and management of servers is handled by the provider so developers can focus on single purpose service and core business problems. The first major serverless offering was back in 2014 by Amazon as AWS Lambda.

Firebase cloud functions (Beta) are relatively new (only a couple of months). Pricing is in 3 tiers, the free tier includes a generous usage limit so you can take an informed decision after trying it out. Since it is backed by Google, easy integrations available with google services like Compute engine and analytics. According to official documentation it is a glue between firebase and cloud services (outbound networking not available in free tier).

And now to answer the big question, how does it work ? You write a piece of JavaScript code (that exposes some functions) that gets stored in Google cloud and runs in a managed node.js environment. These cloud functions are triggered by changes in database, storage; analytics events, new user signups etc. Firebase even provides a way to make it http triggered. Here are few of the capabilities of cloud functions:

  • Real-time database triggers
  • Firebase authentication triggers.
  • Firebase analytics triggers.
  • Cloud storage triggers.
  • Cloud Pub/Sub triggers.
  • HTTP Triggers.

This is a powerful set of features, here are a few of the use cases:

  • Sanitize abusive text content from database (with real-time database triggers).
  • Send welcome email to new users (with authentication triggers).
  • Send a gift coupon as push notification to a user who just purchased something from your app (Firebase analytics triggers).
  • Create and store thumbnail of images, filter out abusive images (Cloud storage triggers, with cloud vision API)
  • Use networking library of your choice to trigger cloud function to send push notification, get data etc. (HTTP triggers)

Setting up firebase cloud functions: (Assuming you already have a firebase base setup in console)

  • Install node.js and npm.
  • Install the latest firebase command line interface (CLI)
    • npm install -g firebase-tools
  • Log in to firebase
    • firebase login
  • initialize firebase project workspace
    • firebase init

      (pick only functions, default selection includes database and hosting as well, select yes for installing dependencies)

This initializes the current directory (that your terminal is pointing to) as firebase workspace for the selected project and creates a directory named functions. functions directory has following contents:

  • package.json – Used by node to describe the project. All the project dependencies are listed here. By default it lists “firebase-admin” and “firebase-functions” as dependencies.
  • node_modules – This is where npm keeps all its dependencies. If you have worked with node.js you already know about this. No need to checkin the entire folder on source control.
  • index.js – This is the entry point for defining all the cloud functions. This is where the server code resides.

Once this is done, create your cloud functions in index.js. Below are a few examples. Lets see some examples first before deploying the cloud functions.

HTTP triggered cloud function:

This cloud function is just performing some random task based on the request. Like reject PUT requests, return user count from real-time database with or without the name parameter that gets passed in the post request. The point is: It can be triggered via a REST end point and has access to firebase features like real-time database.

'use strict'
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

// http triggered cloud function
exports.triggerHttp = functions.https.onRequest((req, resp) => {
    // Forbid PUT requests
    if (req.method === 'PUT') {
        console.log("Forbidden response sent");
        resp.status(403).send('Forbidden!');
    }
    if (req.body.name) {
        return admin.database().ref('/users')
         .once('value').then(allUsers => {
             console.log("NAMED post body");
             resp.send('hi '+req.body.name+'. Total users: ' +
             allUsers.numChildren());
         });
    } else {
       return admin.database().ref('/users')
           .once('value').then(allUsers => {
              console.log("no name post body. user count sent");
              resp.send('Empty post body. Total users: ' +
              allUsers.numChildren());
            });
    }
});

Real-time database triggered cloud function:

These type of triggers can be attached to any node of the firebase real time no-sql JSON database. For every change in that node, the function is triggered. Here is an example of use case discussed above, sanitize abusive text from database.

database_triggered

// Sanitizing comments.
exports.sanitizeComments = functions.database.
    ref('/comments/{commentId}').onWrite(event => {

    const commentId = event.params.commentId;
    const post = event.data.val();
    console.log('commentId: ' + commentId + ": " + post.text);

    if(post.sanitized){
        // prevent infinite looping
        return;
    }

    post.sanitized = true;
    post.text = sanitize(post.text);
    return event.data.ref.set(post);
});

there are a couple of points to note here. The trigger is attached to node root/comment/commentId which has the following child structure

{"artifactId": true,
    "text": "This is a sample comment. change crazy to lovely and stupid to wonderful",
    "commenter": "user_20jd2j2jfj",
    "sanitized": false,
    "timestamp": 1493815657159
}

So for every comment that gets added we can extract the comment id (event.params.commentId)  and data (event.data.val()) from data as shown in JavaScript function above. As soon as this comment is added (the JSON above), the function is triggered and it over writes the data with a sanitized text. To prevent infinite looping (as writing sanitized data in database will again trigger the function and will keep on going till your quota is exhausted) the if check with sanitized flag is in place. Now since writing to database is an asynchronous operation, it returns right away before performing the write operation. To handle this, cloud functions must return a JS Promise, which is succeeds or fails when the asynchronous operation (like write) is complete. This enables firebase to decide whether the operation has been terminated. Refer this link to learn more about JavaScript promises.

In one my apps I am using real-time database triggers to send push notification about a new post. Image below shows sending FCM when a user gains a new follower.

new_follower_triggered_fcm

Firebase Auth triggered cloud function:

Among many offerings, firebase also provides an auth SDK that enables app developers to integrate social login with major identity providers like facebook, google, twitter etc. Once a user logs in with one of these or signs up via email (the firebase way) an event is emitted, this can be used to send welcome email to new users.

exports.sendWelcomeEmail = functions.auth.user()
    .onCreate(event => {
        const user = event.data; // The Firebase user.
        const email = user.email; // The email of the user.
        const displayName = user.displayName; // The display name of the user.
        return sendWelcomeEmail(email, displayName);
});

The function definition states that this event will be triggered when a new user is created. To implement sendWelcomeEmail function any 3rd party providers (paid tiers) or gmail can be used.

Firebase storage triggered cloud function:

When a new image is uploaded, create a thumbnail of the image and send it to client:

storage_triggered

Deploying cloud functions:

  • deploy all – database rules, storage rules, functions and hosting data
    • firebase deploy
  • Deploy partially
    • firebase deploy --only functions

Deployment can take several minutes. After successful deployment, you should see a screen similar to this:

cloud_function_deployed

Notice the function URL for http triggered cloud function. Use this as http end point for triggering.

The Pros:

  • Cloud functions are very helpful in keeping all the business logic centralized in one place and not in multiple clients.
  • Firebase real-time database makes it insanely easy to develop MVP. Database triggered cloud functions enables solving many issues that will otherwise need an app update.
  • No need to learn any other language or server maintenance (that is why it is serverless)
  • Value for money: free and paid quota are much better than what is available out there.
  • Single command for initialization and deployment.

The Cons:

  • Maintaining cloud server code can become a nightmare as your business logic becomes complex.
  • No control over instances.
  • In case of an error like the infinite loop issue mentioned above, there is no alert.
  • Firebase doesn’t support complex database queries yet, this restricts what can be done with firebase functions.
  • Only one language choice as of now.
  • Cloud functions is still in beta and I wouldn’t recommend it for production.
  • To test outbound network requests a paid plan is needed (Flame or Blaze)

I have open sourced 2 apps that uses firebase:

  • SyncroEdit:  A collaborative note editing android app that uses realtime database (check it out here)
  • Vividity: Photo sharing android app using firebase (check it out here). This has an index.js file at its root, covers some of the use cases discussed here.

Let me know what you think. Got any questions? shoot below in comments.

-Kaushal D (@drulabs twitter/github)

drulabs@gmail.com

WiFi direct service discovery in android

wi-fi-direct-service-discovery

I suggest reading my previous posts on NSD and WiFi Direct before reading this one. This post requires some of things discussed in these two posts. If you are already aware of WiFi direct service discovery, you  can directly check out my sample code on git.

Like NSD, we can register and discover services over WiFi direct. The problem I faced with WiFi direct was one prefixed port for initial data transfer (as peer devices were not aware of the port information), after that the port was dynamic, and fixed port was released. Using WiFi direct service discovery we can append additional data (100-200 bytes)  with the advertised service. So unlike WiFi direct, we can request port dynamically and append it with service. No prefixed port.

We all know socket communication, here is a recap just in case:

//Server side
ServerSocket mServer = new ServerSocket(mPort);
Socket socket = null;
while (acceptRequests) {
    // this is a blocking operation
    socket = mServer.accept();
    handleData(socket);
}

//Client side
socket = new Socket(hostIP, hostPort);
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(transferObject);
oos.close();

If you have read my earlier post on WiFi direct, you know almost everything required for WiFi direct service discovery (except appending additional data part). Function is similar to NSD and code is similar to WiFi-Direct.

Adding local service

Same class as WiFi direct is used. Here we use WifiP2pmanager‘s addLocalService() method. Here is the official documentation for this method:

wifip2pmanager_addlocalservice

All the parameters are familiar except for WifiP2pServiceInfo. Similar to NSD, this is a holder object for service information. In this case it is a WiFi P2P service info.

WifiP2pServiceInfo is a class for storing service information that is advertised over a WiFi peer-to-peer setup. It has two direct sub-classes. Both are bonjour service info:

For our use case, which sharing of port and IP information and then finally sharing data with other devices, we will use the former, it allows us to append a string map with the service. The later allows us to append a list of string, which can also be used.

As mentioned earlier, WifiP2pDnsSdServiceInfo allows us to append a string map. here is how to do it.

Map<String, String> record = new HashMap<String, String>();
record.put(KEY_BUDDY_NAME, player == null ? Build.MANUFACTURER : player);
record.put(KEY_PORT_NUMBER, String.valueOf(port));
record.put(KEY_DEVICE_STATUS, "available");
record.put(KEY_WIFI_IP, Utility.getWiFiIPAddress(context));

WifiP2pDnsSdServiceInfo service = WifiP2pDnsSdServiceInfo.newInstance(
    SERVICE_INSTANCE, SERVICE_TYPE, record);
wifiP2pManager.addLocalService(wifip2pChannel, service, new WifiP2pManager.ActionListener() {

    @Override
    public void onSuccess() {
        Log.d(TAG, "Added Local Service");
    }

    @Override
    public void onFailure(int error) {
        Log.e(TAG, "ERRORCEPTION: Failed to add a service");
    }
});

As you can see in the sample code above, a map called record is passed when creating instance of service info object. After this you are done with advertising your WiFi direct service.

Discovering WiFi direct services

Discovery requires adding a service discovery request in WiFi direct via WifiP2pManager‘s addServiceRequest() method. Here is description from the official site:

wifip2pmanager_addservicerequest

After this a the service discovery request must be issued. Set the type of service you want to discover DNS-SD or UPNP and issue the request.

serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
wifiP2pManager.addServiceRequest(wifip2pChannel, serviceRequest,
new WifiP2pManager.ActionListener() {

    @Override
    public void onSuccess() {
        Log.d(TAG, "Added service discovery request");
    }

    @Override
    public void onFailure(int arg0) {
        Log.d(TAG, "ERRORCEPTION: Failed adding service discovery request");
    }
});
wifiP2pManager.discoverServices(wifip2pChannel, new WifiP2pManager.ActionListener() {

	@Override
	public void onSuccess() {
		Log.d(TAG, "Service discovery initiated");
	}

	@Override
	public void onFailure(int arg0) {
		Log.d(TAG, "Service discovery failed: " + arg0);
	}
});

There is a reason code associated with every failure callback. Check those error code if you get a failure callback.

The map that was advertised is received in DnsSdTxtRecordListener interface callback. This needs to be set to receive the map. And DnsSdServiceResponseListener is callback for receiving the advertised service. Here is how to set it:

wifiP2pManager.setDnsSdResponseListeners(wifip2pChannel,
new WifiP2pManager.DnsSdServiceResponseListener() {

    @Override
    public void onDnsSdServiceAvailable(String instanceName,
        String registrationType, WifiP2pDevice srcDevice) {

        // A service has been discovered. Is this our app?
        if (instanceName.equalsIgnoreCase(SERVICE_INSTANCE)) {
            // yes it is
         } else {
            //no it isn't
        }
    }
}, new WifiP2pManager.DnsSdTxtRecordListener() {

    @Override
    public void onDnsSdTxtRecordAvailable(
            String fullDomainName, Map<String, String> record,
            WifiP2pDevice device) {
        boolean isGroupOwner = device.isGroupOwner();
        peerPort = Integer.parseInt(record.get(TransferConstants.KEY_PORT_NUMBER).toString());
			// further process
    }
});

here in the text record callback you can see the map is received with whatever info was set. After this it is same as WiFi direct. you can check out my earlier post for this.

You need to have a connection info listener same as WiFi direct example (old post), and you need to connect with the device, with info received as a part of DnsSdTxtRecordAvailable or DnsSdServiceAvailable callbacks (above code example).

In the code example above you can see that the port information is received as a part of string map in text record available method, and in connection info callback you can get the IP address of the group owner of the current WiFi group. After that, its the same age old socket communication.

Check out the google sample for this. My sample app source is available on git.

Happy coding!!!

Let me know what you think. Got any questions? shoot below in comments.

-Kaushal D (@drulabs twitter/github)

drulabs@gmail.com

Local networking in android – WiFi direct

wifi-direct

In my earlier blog post I discussed data sharing between two android devices in same network using NSD. In this post I will explain communication between two non-connected android devices (can be connected to same or other network, doesn’t really matter) via WiFi direct. Devices should be in WiFi range. I will start with a bit of theory about WiFi direct and then we will see how it is implementable using android APIs (Sample app source code git link is at the end of this post).

Same as earlier post the problem addressed is sharing IP and port information. Communication again will be socket communication.

WiFi direct is a WiFi certified standard enabling devices to connect with each other without requiring a wireless access point (a.k.a router or WiFi hot spot). Using this, devices can communicate with each other at typical WiFi speeds, unlike ad hoc wireless connections (which allows two or more devices to connect and communicate, and has a limit of 11 Mbps) or Bluetooth, setup required for WiFi direct is much simpler. Here each member is assigned a limited access point and other members connect to it as regular clients. WPS and WPA2 are used for encryption and keep the communication private.

The peer device acting as current access point is said to be assuming a group owner role in WiFi direct group. A WiFi direct group consists of 1 group owner and devices or peers connected to it as clients (P2P clients). The group owner device sets properties for communication like operating channel, whether the group is persistent, encryption type etc. Any compatible device with right hardware and android ICS or above (API 14+) can assume group owner role. After role negotiation (group owner or P2P client), devices assume their decided role, and group owner starts operating in access point mode (this access point will not be visible under available networks of mobile devices).

Now coming to android. Android’s WiFi P2P framework complies with WiFi direct certification program. It consists of:

  • Methods that allows us to discover, request and connect to peers (android.net.wifi.p2p.WifiP2pManager).
  • Listeners that notifies us of success and failure of WifiP2pmanager’s method calls.
  • Intents to notify specific events, such as new peer joined, connection dropped etc. (like WIFI_P2P_PEERS_CHANGED_ACTION and WIFI_P2P_CONNECTION_CHANGED_ACTION)

Discovering peers

You need the following permissions for using WiFi direct for communication

    <uses-permission android:name="android.permission.INTERNET" android:required="true"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" android:required="true"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" android:required="true"/>

Internet permission is required for using sockets anyway.

wifiP2pManager.discoverPeers(wifip2pChannel, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        // discovery has been successfully started
    }

    @Override
    public void onFailure(int reasonCode) {
        // discovery failed to start. checkout reason code
    }
});

The above method is all you need to start the WiFi direct peer discovery. If you get a callback in failure method, check the reason code for it.

Getting peer list

Getting peer list is tricky, as WifiP2pManager will not give you that. This is done via broadcast receivers registered dynamically (not in the manifest file).

wifi-direct-broadcast-receiver-01

The second filter action in above code (the image) is what is triggered when a new peer has joined (or left). When broadcast with this action is received we can request peers from WifiP2pManager. It takes a callback to return entire peer list not just the new ones, so if you are displaying a list, clear the list and add all the peers received in the callback. here is the broadcast receiver’s code:

if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
    // request available peers from the wifi p2p manager. This is an
    // asynchronous call and the calling activity is notified with a
    // callback on PeerListListener.onPeersAvailable() of passed activity
    // the activity implements the listener interface
    if (wiFiP2pManager != null) {
        wiFiP2pManager.requestPeers(channel, activity);
    }
}

The callback that is overridden will be called asynchronously. Peer list can be extracted from there.

@Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
    List<WifiP2pDevice> devices = (new ArrayList<>());
    devices.addAll(peerList.getDeviceList());

    //do something with the device list
}

So now we have the peer list. We can display this list to user or call connect with each peer. Once the peer is discovered we need to send a connection request to connect and form a WiFi direct group.

Connecting with a peer

This step requires WiFi mac of the device. It is received as a part of peer info in discovery step. Here are the properties of WifiP2pDevice:

wifip2pdevice_states

The WifiP2pDevice list that we received in OnPeersAvailable() method of PeerListListener callback has exactly what we need to make a connection request.

Here is how to make a connection request using WifiP2pManager.

WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = //set device address from WifiP2pDevice received;
config.wps.setup = WpsInfo.PBC;
config.groupOwnerIntent = 4;
wifiP2pManager.connect(wifip2pChannel, config, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        // Connection request successfully sent
    }

    @Override
    public void onFailure(int reasonCode) {
        // Failed to send connection request.
    }
});

Just create a config object set channel properties and call the connect method. The group owner intent value is for setting the probability of the device to become group owner. It’s value varies from 0-15. 15 means highest chance of the connection request sender device to become group owner. But whatever code you write, you must handle both group owner and a regular P2P client scenario.

Once the request is sent to a device, the device user must accept the connection request from system prompt. After that role negotiation happens and devices move to their decided roles. After successful connection, a connection info request can be issued via WifiP2pManager. Which sends callback to WifiP2pManager.ConnectionInfoListener. It has only one method:

wifip2pmanager-connectioninfolistener

This connection info object WifiP2pInfo contains the IP information of group owner. Check below:

wifip2pinfo

So now, our first problem is addressed, here we got the IP address of the group owner. And we also know whether the current device is group owner or not. So basically every peer in the group knows the group owner’s IP address.

The remaining problem is sharing the port it is listening on. I couldn’t find any proper solution for this, I solved it by prefixing the port, then sharing the dynamic port data with clients, and then clients can share their own info and dynamic port. So first communication happens over the fixed port and after that, then dynamic port data number is transferred, and then it is regular socket communication. Check out my sample app for this.

Even this problem can be solved using the WiFi direct service discovery. Watch out for my next post on this topic.

One last thing to cover in this post is method called createGroup(). This method is for supporting the legacy devices with no WiFi direct hardware. This basically an access point and any device with WiFi capability can connect with it like a regular access point and share data like we did in my previous post. On top of that it can work as expected in WiFi direct scenario, so support both types of devices.

Sample app code available in github.

Happy coding!!!

Let me know what you think. Got any questions? shoot below in comments.

-Kaushal D (@drulabs twitter/github)

drulabs@gmail.com

Local networking using NSD (Network service discovery)

nsd_blog

In this post we will see how data can be transferred between two android devices in the same network. What follows is a bit of theory and implementation in android studio. There is a source code link at the bottom of the page for reference.

In LAN communication a server listens for client requests in one port (with a blocking connection) and client connects and communicates with server using the IP address and port information. This is basic socket communication in client-server architecture.

LAN communication have been around since ages, be it between laptops, desktops or other mobile devices. Socket communication is nothing new in the world on networking. However, there are mainly 2 problems associated with it:

  • Device(s) is/are not aware of the IP address of other device(s).
  • Device(s) do not know the port other device(s) is/are listening on.

The 2nd problem mentioned above is solvable by prefixing a port. Say port 35627 is fixed for our particular application, so once we know the IP address we know the port we have fixed for our application. But this has other side-effects. OS usually gets any free port when any application requests it or for its own purposes. If the fixed port is used by any other application and you try to use it, you will get a port already in use exception. To avoid this, the port must be requested dynamically from the OS, and OS will provide you any free port (hence no chances of port already in use exception).

And other device(s) must be aware of this port and IP information for communicating with it.

With NSD (Network service discovery), this very problem is addressed. Communication happens over socket like always, but NSD equips us with ways to share the IP and port information. Lets see what NSD is all about, and how to solve the mentioned problem using NSD.

Android NSD allows our app to identify devices in the same network that offer the services we are requesting. With NSD we can register, discover and connect with our service (or other services) over network. Service is basically some information that is advertised over network. Similar to a broadcast receiver and intent broadcast in android. Device 1 advertises some service say Multi-player chess and device 2 discovers it, connect with it and LAN chess use case solved.

Basic steps involved in NSD are depicted in below figure:

nsd1nsd2

As you can see from above, steps involved in NSD are as follows:

  • Application starts and registers a service over network (optional step, avoid if all you want is to discover a service).
  • App starts service discovery over network.
  • In callback method a service object is received when service is FOUND.
  • In callback method a service object is received when service is LOST.
  • resolve the found service and extract the port and IP information of the service advertiser.
  • Use socket to establish a connection and communicate.

Once port and IP information is retrieved by resolving the found service, the socket communication (yes the ye-old socket communication) is used to transfer data. Lets see these are done in code.

Registering your service

Android’s NsdManager class is used for this. simply call registerService.

NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setPort(port);
serviceInfo.setServiceName(mServiceName);
serviceInfo.setServiceType(SERVICE_TYPE);
mNsdManager.registerService(
      serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);

An NsdServiceInfo object needs to be created and passed in register service method for registration. It holds the service information to be advertised. Service name can be any name you want your service to have, a simple string. Service type is also a string and needs to be in “_<protocol>._<transportLayer>” format. You can choose the type of service from IANA. For the demo I created I simply used an unregistered one “_localdash._tcp“.

Next parameter is protocol, please use the one mentioned in the code, I didn’t find any other protocol. DNS_SD stands for Domain Name System Service Discovery.

The registration listener passed as the last parameter in register service method is callback. It contains 4 methods related to registration. Registration listener’s callback methods are when registration is successful, or failed, or un-registration happens etc. Click on this link to know more.

Callback on Registration listener’s onServiceRegistered() method means service has been successfully advertised. Next is discovering the advertised services.

Discovering services

For discovering services using NSD we use the discoverServices() method (of course).

mNsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

Here we are telling NsdManager which type of services to discover. The callback here is Discovery listener. This callback informs us when a service is found or lost. It has many callback methods, the one that is important to us is onServiceFound(). Click here to know about other callback methods of discovery listener. For every failure method there is a reason code in parameter. The service found here can be a different service than what we are expecting make sure to verify service type and service name before proceeding. The source for my demo app is shared on github, do check it out to know how to do it (github link). The NsdServiceInfo object in onServiceFound() method cannot be used yet. It needs to be resolved first.

Resolving discovered services

Services can be resolved via resolveService() method of NsdManager. Here is the code for it:

mNsdManager.resolveService(service, mResolveListener)

This is called in service found method of discovery listener (after confirming it is our service). The call back Resolve listener has two methods. on service resolved and on resolve failed (with error code). Once service resolved callback is received, we can use the service info object in its parameter to retrieve IP and port information. Here is how to do it:

NsdServiceInfo serviceInfo = //The service object in OnServiceResolved
String ipAddress = serviceInfo.getHost().getHostAddress();
int port = serviceInfo.getPort();
//Data sending service
DataSender.sendCurrentDeviceData(LocalDashNSD.this, ipAddress, port);

Here we got the service advertisers IP address and port information set by him (or it, the advertiser) when advertising the service. Now Bang!!! socket communication can happen. This happened without any mischievous stuff or hard coding or any magic numbers. Everything is real dynamic.

If you don’t know how to perform basic socket communication or you want to check out all the steps we discussed here check out my demo app code on github:

Happy coding!!!

Let me know what you think. Got any questions? shoot below in comments.

-Kaushal D (@drulabs twitter/github)

drulabs@gmail.com