We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Becoming a backend developer - Part 3: Connecting to the server from a mobile app

  • Suragch
March 28th, 2019
You need experience in at least one of Android, iOS or Flutter development.

In this three-part series, we have been covering all the basics of what it takes to become a backend developer. This is a tutorial for mobile app developers.

Introduction

So far we have already learned about REST APIs and how they use the HTTP protocol for client server communication. In the last lesson, we learned how to implement our API on the server, once using Node.js and again using Server Side Dart. I chose Node.js because it is popular and Server Side Dart because it allows Flutter developers to use Dart everywhere. However, it really doesn't matter what platform or language you chose for the backend server. Because we are using a REST API, we are able to make requests to it with Android, iOS, Futter, or any other frontend platform.

In this tutorial, we will look at how to make a frontend client app that connects to the server we made in part two, I’ll give you three different examples: one for Android, one for iOS, and one for Flutter. Since you are already a mobile app developer, I won't go into much detail about how to create the app or build the layout. Instead I'll give you the code for making the HTTP requests.

Just scroll down to the platform you are developing with. If you are a developer for a platform other than Android, iOS, or Flutter, you can look up the code for making HTTP requests on your platform and just port one of the examples below.

Prerequisites

I'm assuming that you already have experience with Android, iOS, or Flutter, and that you have the development environment set up. I wrote and tested the client apps using the following software versions:

  • Android: Android Studio 3.3
  • iOS: Xcode 10.1
  • Flutter: Android Studio 3.3 with Flutter 1.2.1

Android client app

Create a layout similar to the image below (see source code):

In the manifest add the INTERNET permission (see source code):

    <uses-permission android:name="android.permission.INTERNET" />

And allow clear text:

    <application
        android:usesCleartextTraffic="true"
        ...
        >

Note: You should use a secure HTTPS server in production, but in this tutorial we are using "clear text" HTTP. Doing this allowed me to simplify the server tutorial in part two by not having to register a certificate with a certificate authority.

Replace MainActivity.kt with the following code (Java version here):

    package com.example.backendclient

    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.os.AsyncTask
    import android.util.Log
    import android.view.View
    import java.io.*
    import java.net.HttpURLConnection
    import java.net.URL

    private const val HOST = "http://10.0.2.2:3000"
    private const val TAG = "TAG"

    class MainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }

        fun makeGetAllRequest(view: View) {
            HttpGetRequest().execute(HOST)
        }

        fun makeGetOneRequest(view: View) {
            val idToGet = 0
            val url = "$HOST/$idToGet"
            HttpGetRequest().execute(url)
        }

        fun makePostRequest(view: View) {
            val json = "{\"fruit\": \"pear\", \"color\": \"green\"}"
            HttpPostRequest().execute(HOST, json)
        }

        fun makePutRequest(view: View) {
            val idToReplace = 0
            val url = "$HOST/$idToReplace"
            val json = "{\"fruit\": \"watermellon\", \"color\": \"red and green\"}"
            HttpPutRequest().execute(url, json)
        }

        fun makePatchRequest(view: View) {
            val idToUpdate = 0
            val url = "$HOST/$idToUpdate"
            val json = "{\"color\": \"green\"}"
            HttpPatchRequest().execute(url, json)
        }

        fun makeDeleteRequest(view: View) {
            val idToDelete = 0
            val url = "$HOST/$idToDelete"
            HttpDeleteRequest().execute(url)
        }

        // GET
        class HttpGetRequest : AsyncTask<String, Void, Void>() {
            override fun doInBackground(vararg params: String): Void? {
                val urlString = params[0]

                val myUrl = URL(urlString)
                val connection = myUrl.openConnection() as HttpURLConnection
                connection.requestMethod = "GET"
                val result = getStringFromInputStream(connection.inputStream)
                val statusCode = connection.responseCode
                connection.disconnect()

                Log.i(TAG, "GET result: $statusCode $result")
                return null
            }
        }

        // POST
        class HttpPostRequest : AsyncTask<String, Void, Void>() {
            override fun doInBackground(vararg params: String): Void? {
                val urlString = params[0]
                val json = params[1]

                val myUrl = URL(urlString)
                val connection = myUrl.openConnection() as HttpURLConnection
                connection.requestMethod = "POST"
                connection.doOutput = true
                connection.setRequestProperty("Content-Type", "application/json")

                writeStringToOutputStream(json, connection.outputStream)
                val result = getStringFromInputStream(connection.inputStream)
                val statusCode = connection.responseCode
                connection.disconnect()

                Log.i(TAG, "POST result: $statusCode $result")
                return null
            }
        }

        // PUT
        class HttpPutRequest : AsyncTask<String, Void, Void>() {
            override fun doInBackground(vararg params: String): Void? {
                val urlString = params[0]
                val json = params[1]

                val myUrl = URL(urlString)
                val connection = myUrl.openConnection() as HttpURLConnection
                connection.requestMethod = "PUT"
                connection.doOutput = true
                connection.setRequestProperty("Content-Type", "application/json")

                writeStringToOutputStream(json, connection.outputStream)
                val result = getStringFromInputStream(connection.inputStream)
                val statusCode = connection.responseCode
                connection.disconnect()

                Log.i(TAG, "PUT result: $statusCode $result")
                return null
            }
        }

        // PATCH
        class HttpPatchRequest : AsyncTask<String, Void, Void>() {
            override fun doInBackground(vararg params: String): Void? {
                val urlString = params[0]
                val json = params[1]

                val myUrl = URL(urlString)
                val connection = myUrl.openConnection() as HttpURLConnection
                connection.requestMethod = "PATCH"
                connection.doOutput = true
                connection.setRequestProperty("Content-Type", "application/json")

                writeStringToOutputStream(json, connection.outputStream)
                val result = getStringFromInputStream(connection.inputStream)
                val statusCode = connection.responseCode
                connection.disconnect()

                Log.i(TAG, "PATCH result: $statusCode $result")
                return null
            }
        }

        // DELETE
        class HttpDeleteRequest : AsyncTask<String, Void, Void>() {
            override fun doInBackground(vararg params: String): Void? {
                val urlString = params[0]

                val myUrl = URL(urlString)
                val connection = myUrl.openConnection() as HttpURLConnection
                connection.requestMethod = "DELETE"

                val result = getStringFromInputStream(connection.inputStream)
                val statusCode = connection.responseCode
                connection.disconnect()

                Log.i(TAG, "DELETE result: $statusCode $result")
                return null
            }
        }
    }

    private fun writeStringToOutputStream(json: String, outputStream: OutputStream) {
        val bytes = json.toByteArray(charset("UTF-8")) // API 19: StandardCharsets.UTF_8
        outputStream.write(bytes)
        outputStream.close()
    }

    private fun getStringFromInputStream(stream: InputStream): String {
        val text =  stream.bufferedReader().use { it.readText() }
        stream.close()
        return text
    }

As you can see, Android uses HttpURLConnection to make HTTP requests. After opening the connection you use setRequestMethod() to choose the HTTP verb that you want (GET, POST, etc.).

You send the request by writing data to an output stream. After that you get the response by reading from an input stream. This should all be done in an AsyncTask to avoid blocking the UI thread.

I used a raw string for the JSON in the code above. The GSON library is one option for converting JSON strings to Java objects. Check out this tutorial for some instruction on that.

With the server that you made in part two running, test the app in the Android emulator. In the Android Studio Logcat, note the statements that get printed after server responses:

    GET result: 200 [{"fruit":"apple","color":"red"},{"fruit":"banana","color":"yellow"}]
    GET result: 200 {"fruit":"apple","color":"red"}
    POST result: 200 Item added with id 2
    PUT result: 200 Item replaced at id 0
    PATCH result: 200 Item updated at id 0
    DELETE result: 200 Item deleted at id 0

iOS client app

Create a layout similar to the image below:

In the Info.plist file, add the following key:

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

Note: You should use a secure HTTPS server in production, but in this tutorial we are using unencrypted text with an HTTP server. Adding the key above bypasses iOS's requirement for encrypted text over a network call. Doing this allowed me to simplify the server tutorial in part two by not having to register a certificate with a certificate authority.

Replace ViewController.swift with the following code:

    import UIKit
    class ViewController: UIViewController {

        let host = "http://localhost:3000"

        // make GET (all) request
        @IBAction func makeGetAllRequestTapped(_ sender: UIButton) {

            guard let url  = URL(string: host) else {return}

            // background task to make request with URLSession
            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let jsonString = String(data: body, encoding: String.Encoding.utf8) 
                    else {return}

                print("GET result: \(statusCode) \(jsonString)")
            }

            // start the task
            task.resume()
        }

        // make GET (one) request
        @IBAction func makeGetOneRequestTapped(_ sender: UIButton) {

            let idToGet = 0;
            let urlString = "\(host)/\(idToGet)"
            guard let url  = URL(string: urlString) else {return}

            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let jsonString = String(data: body, encoding: String.Encoding.utf8) 
                    else {return}

                print("GET result: \(statusCode) \(jsonString)")
            }
            task.resume()
        }

        // make POST request
        @IBAction func makePostRequestTapped(_ sender: UIButton) {

            let dictionary = ["fruit" : "pear", "color" : "green"]

            // prepare request
            guard let url  = URL(string: host) else {return}
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            guard let json = try? JSONSerialization.data(
                withJSONObject: dictionary, options: [])
                else {return}
            request.httpBody = json

            let task = URLSession.shared.dataTask(with: request) { 
                (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let responseString = String(data: body, encoding: .utf8) 
                    else {return}
                print("POST result: \(statusCode) \(responseString)")

                // If your API returns JSON you could do the following:
                // guard let jsonString = try? JSONSerialization.jsonObject(
                //     with: body, options: []) else {return}
            }
            task.resume()
        }

        // make PUT request
        @IBAction func makePutRequestTapped(_ sender: UIButton) {

            let dictionary = ["fruit" : "watermellon", "color" : "red and green"]

            let idToPut = 0;
            let urlString = "\(host)/\(idToPut)"

            // prepare request
            guard let url  = URL(string: urlString) else {return}
            var request = URLRequest(url: url)
            request.httpMethod = "PUT"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            guard let json = try? JSONSerialization.data(
                withJSONObject: dictionary, options: []) 
                else {return}
            request.httpBody = json

            let task = URLSession.shared.dataTask(with: request) { 
                (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let responseString = String(data: body, encoding: .utf8) 
                    else {return}
                print("PUT result: \(statusCode) \(responseString)")
            }
            task.resume()
        }

        // make PATCH request
        @IBAction func makePatchRequestTapped(_ sender: UIButton) {

            let dictionary = ["color" : "green"]

            let idToPatch = 0;
            let urlString = "\(host)/\(idToPatch)"

            // prepare request
            guard let url  = URL(string: urlString) else {return}
            var request = URLRequest(url: url)
            request.httpMethod = "PATCH"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            guard let json = try? JSONSerialization.data(
                withJSONObject: dictionary, options: []) 
                else {return}
            request.httpBody = json

            let task = URLSession.shared.dataTask(with: request) { 
                (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let responseString = String(data: body, encoding: .utf8) 
                    else {return}
                print("PATCH result: \(statusCode) \(responseString)")
            }
            task.resume()
        }

        // make DELETE request
        @IBAction func makeDeleteRequestTapped(_ sender: UIButton) {

            let idToDelete = 0;
            let urlString = "\(host)/\(idToDelete)"

            // prepare request
            guard let url  = URL(string: urlString) else {return}
            var request = URLRequest(url: url)
            request.httpMethod = "DELETE"

            let task = URLSession.shared.dataTask(with: request) { 
                (data, response, error) in
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode 
                    else {return}
                guard let body = data 
                    else {return}
                guard let responseString = String(data: body, encoding: .utf8) 
                    else {return}
                print("DELETE result: \(statusCode) \(responseString)")
            }
            task.resume()
        }
    }

As you can see, iOS uses URLSession to make HTTP requests as URLRequest. It will return a URLResponse from the server. We used JSONSerialization to convert the JSON strings to and from Swift Dictionary objects.

With the server that you made in part two running, test the app in the iOS simulator. Note the log statements that get printed in Xcode after server responses:

    GET result: 200 [{"fruit":"apple","color":"red"},{"fruit":"banana","color":"yellow"}]
    GET result: 200 {"fruit":"apple","color":"red"}
    POST result: 200 Item added with id 2
    PUT result: 200 Item replaced at id 0
    PATCH result: 200 Item updated at id 0
    DELETE result: 200 Item deleted

See also:

Flutter client app

Good job if you chose Flutter. You write the code once and it works for both Android and iOS. Having already made the client app for Android and iOS, I can tell you that Flutter cuts your time in half.

We will have the following layout:

Add the http dependency to your pubspec.yaml file.

    dependencies:
      http: ^0.12.0+1

Replace main.dart with the following code:

    import 'dart:io';
    import 'package:flutter/material.dart';
    import 'package:http/http.dart';

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

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            appBar: AppBar(title: Text('Client App (Flutter)')),
            body: BodyWidget(),
          ),
        );
      }
    }

    class BodyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Align(
          alignment: Alignment.topCenter,
          child: Padding(
            padding: const EdgeInsets.only(top: 32.0),
            child: SizedBox(
              width: 200,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  RaisedButton(
                    child: Text('Make GET (all) request'),
                    onPressed: () {
                      _makeGetAllRequest();
                    },
                  ),
                  RaisedButton(
                    child: Text('Make GET (one) request'),
                    onPressed: () {
                      _makeGetOneRequest();
                    },
                  ),
                  RaisedButton(
                    child: Text('Make POST request'),
                    onPressed: () {
                      _makePostRequest();
                    },
                  ),
                  RaisedButton(
                    child: Text('Make PUT request'),
                    onPressed: () {
                      _makePutRequest();
                    },
                  ),
                  RaisedButton(
                    child: Text('Make PATCH request'),
                    onPressed: () {
                      _makePatchRequest();
                    },
                  ),
                  RaisedButton(
                    child: Text('Make DELETE request'),
                    onPressed: () {
                      _makeDeleteRequest();
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }

      static const Map<String, String> headers = {"Content-type": "application/json"};

      // access localhost from the emulator/simulator
      String _hostname() {
        if (Platform.isAndroid)
          return 'http://10.0.2.2:3000';
        else
          return 'http://localhost:3000';
      }

      // GET all
      _makeGetAllRequest() async {
        // get everything
        Response response = await get(_hostname());
        // examples of info available in response
        int statusCode = response.statusCode;
        String jsonString = response.body;
        print('Status: $statusCode, $jsonString');
      }

      // GET one
      _makeGetOneRequest() async {
        // only get a single item at index 0
        int idToGet = 0;
        String url = '${_hostname()}/$idToGet';
        Response response = await get(url);
        int statusCode = response.statusCode;
        String jsonString = response.body;
        print('Status: $statusCode, $jsonString');
      }

      // POST
      _makePostRequest() async {
        // set up POST request arguments
        String json = '{"fruit": "pear", "color": "green"}';
        // make POST request
        Response response = await post(_hostname(), headers: headers, body: json);
        int statusCode = response.statusCode;
        String body = response.body;
        print('Status: $statusCode, $body');
      }

      // PUT
      _makePutRequest() async {
        // set up PUT request arguments
        int idToReplace = 0;
        String url = '${_hostname()}/$idToReplace';
        String json = '{"fruit": "watermellon", "color": "red and green"}';
        // make PUT request
        Response response = await put(url, headers: headers, body: json);
        int statusCode = response.statusCode;
        String body = response.body;
        print('Status: $statusCode, $body');
      }

      // PATCH
      _makePatchRequest() async {
        // set up PATCH request arguments
        int idToUpdate = 0;
        String url = '${_hostname()}/$idToUpdate';
        String json = '{"color": "green"}';
        // make PATCH request
        Response response = await patch(url, headers: headers, body: json);
        int statusCode = response.statusCode;
        String body = response.body;
        print('Status: $statusCode, $body');
      }

      // DELETE
      void _makeDeleteRequest() async {
        // set up DELETE request argument
        int idToDelete = 0;
        String url = '${_hostname()}/$idToDelete';
        // make DELETE request
        Response response = await delete(url);
        int statusCode = response.statusCode;
        String body = response.body;
        print('Status: $statusCode, $body');
      }
    }

    // For help converting JSON to objects in Flutter see
    // this post https://stackoverflow.com/a/54657953
    class Fruit {

      int id;
      String fruit;
      String color;

      Fruit(this.fruit, this.color);

      // named constructor
      Fruit.fromJson(Map<String, dynamic> json)
          : fruit = json['fruit'],
            color = json['color'];

      // method
      Map<String, dynamic> toJson() {
        return {
          'fruit': fruit,
          'color': color,
        };
      }
    }

We used the http package to make the requests. We get back a Response object from which we can get the status code and body. Although we didn’t use it here, I added a model object at the end that included the code to convert JSON strings to and from Map objects.

With the server that you made in part two running, test the Flutter app in the Android emulator or iOS simulator. Note the log statement that Android Studio prints in the Run tab:

    GET result: 200 [{"fruit":"apple","color":"red"},{"fruit":"banana","color":"yellow"}]
    GET result: 200 {"fruit":"apple","color":"red"}
    POST result: 200 Item added with id 2
    PUT result: 200 Item replaced at id 0
    PATCH result: 200 Item updated at id 0
    DELETE result: 200 Item deleted at id 0

Conclusion

We’ve covered a lot in this series. It’s my hope that this will be a solid start to your backend development work. Starting out on a new technology is the most difficult step. It will get easier from here.

The essential files for each of the server and client examples in this series are on GitHub.

Going on

You already have a working server. However, the following topics are some things you will want to work on before your server is ready for production.

Database

In the server examples in part two, we used an array as a mock database. Later, of course, you’ll want to add a real database. The “further study” links I included at the end of both server sections tell how to do that.

HTTPS

Modern versions of Android and iOS require secure encrypted connections by default when accessing the internet. We bypassed that security when we made the client apps in above so that we wouldn’t have to bother registering with a certificate authority.

Don’t bypass it in your production apps, though! It’s not a lot more work to set it up on the server and you can get a free certificate from Let’s Encrypt. (I wouldn’t recommend using a self-signed certificate.)

Authentication, validation, and testing

If you put your server online now, anyone in the world could mess your database up. It’s probably fine to leave your GET methods open to the world as long as there is no sensitive data, but you will surely want to add some sort of authentication for who is allowed to POST, PUT, PATCH, and DELETE. And even when users are authenticated, never trust what they send you. Validate any data you receive.

Node.js and Server Side Dart both support unit testing. You really need to write tests. The good news with backend programming is that you don’t have the UI to deal with.

Publishing

When you are ready to deploy the server, you might consider getting a VPS running Linux. This is convenient because getting the server up and running is essentially the same as doing it on your local machine.

I've found quite a few VPSs on LowEndBox for under $20 USD per year. They’re great for learning and even for small production apps. (Every now and then a company goes out of business, though, so keep backups)

In the future when reliability and scalability become more important, you can consider deploying to one of the big-name cloud hosting providers.

It is also recommended to put a reverse proxy between your server app and the internet. Nginx works well for this. See the following links for help:

Clone the project repository
  • Android
  • Dart
  • Flutter
  • iOS
  • JavaScript
  • Kotlin
  • Node.js
  • Swift
  • no pusher tech

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.