Building NodeJS REST service and consuming it from within Android application

Building NodeJS REST service and consuming it from within Android application

Introduction

In this tutorial I will walk you through the process of building REST service in NodeJS. That service will enable Android users to authenticate to the back-end and set their profile photo.

Using the code

The application consist of two parts:
  • Backend: RESTful service written in NodeJS
  • Mobile: Android app written in Java

Part 1. - NodeJS back-end

Back-end part will enable users to:
  • Login using email/password combination
  • Upload their profile picture
The source code is located on Github. For simplicity reasons here I am going to highlight only the important parts of the code.

app.js
var express = require('express');
var http = require('http');
var path = require('path');
var app = express();
var util = require('util');
var multiparty = require('multiparty');

var multer = require('multer');
var upload = multer({ dest: './uploads/' });
app.use(multer({ dest: './uploads/' }));
var fs = require('fs');
 
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(require('stylus').middleware(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));

app.use('/uploads', express.static(__dirname + '/uploads'));
app.use('/uploads', express.directory(__dirname + '/uploadsimages'));

// development only test
if ('development' == app.get('env')) {
    app.use(express.errorHandler());
}
var testEmail = "test@email.com";
var testPassword = "123456";
var testuserid = "my_userid";

app.get('/', function (req,res) {
  res.send("BCG REST service v2 by Jalle");
  });
var type = upload.single('image');

//working
app.post('/sessions/new', function (req, res) {
  var email = req.body.email;
  var password = req.body.password;
  if (email == testEmail && password == testPassword) {
    res.json({ userid: "my_userid", token: "token" });
  } else {
    res.json({ userid: "", token: "" });
  }
});


//working
app.get('/users/:userid', function (req, res) {
  var userid = req.params.userid;
  if (userid == testuserid ) {
    res.json({ email: testEmail, avatar_url: "uploads/pic.jpg" });
  } else {
    res.json({ email: "", avatar_url: "" });
  }
});

//upload avatar image
app.post('/users/:userid/avatar/:avatar', type,
  function(req, res) {
     
    var userid = req.params.userid;
    if (userid == testuserid || true) {
      var tmp_path = req.file.path;
      /** The original name of the uploaded file      stored in the variable "originalname". **/
      var target_path = 'uploads/' + req.file.originalname;
      /** A better way to copy the uploaded file. **/
      var src = fs.createReadStream(tmp_path);
      var dest = fs.createWriteStream(target_path);
      src.pipe(dest);
      src.on('end', function () { res.end('file uploaded'); });
      src.on('error', function (err) { res.end('error'); });

      res.json({ avatar_url: target_path });
    } else {
      res.json({ avatar_url: "" });
    }
  });


//File upload test
app.get('/upload', function (req, res) {
  res.render('upload', {
    title: 'Upload Images'
  });
});
app.post('/upload', type, function (req, res, next) {
  /** When using the "single"      data come in "req.file" regardless of the attribute "name". **/
  var tmp_path = req.file.path;
  /** The original name of the uploaded file      stored in the variable "originalname". **/
  var target_path = 'uploads/' + req.file.originalname;
  /** A better way to copy the uploaded file. **/
  var src = fs.createReadStream(tmp_path);
  var dest = fs.createWriteStream(target_path);
  src.pipe(dest);
  src.on('end', function () { res.end('file uploaded'); });
  src.on('error', function (err) { res.end('error'); });
});


http.createServer(app).listen(app.get('port'), function () {
    console.log('Express server listening on port ' + app.get('port'));
});

Now we can test the backend by using Postman. Use test account (credentials are in the code) to login and submit profile picture.
Source code available at Github

Part 2. - Android application


Mobile app will contain these classes
  • LoginActivity
  • MainActivity
  • APIInterface
LoginActivity
package com.bcg.loginexample;

/**
 * A login screen that offers login via email/password.
 */
public class LoginActivity extends AppCompatActivity {
    public Settings settings;

    public static final String BASE_URL = ApiInterface.ENDPOINT;

    // UI references.
    private EditText mEmailView;
    private EditText mPasswordView;
    private TextView failedLoginMessage;

    View focusView = null;
    private String email;
    private String password;

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

         settings =new Settings(getApplicationContext());
         settings = settings.getSettings();

     //  settings.saveSettings("","","","");
        // Set up the login form.
        mEmailView = (EditText) findViewById(R.id.email);
        failedLoginMessage = (TextView)findViewById(R.id.failed_login);

        mPasswordView = (EditText) findViewById(R.id.password);
        mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == R.id.login || id == EditorInfo.IME_NULL) {
                    attemptLogin();
                    return true;
                }
                return false;
            }
        });

        Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
        mEmailSignInButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
              attemptLogin();

            }
        });

         if(settings.token!="")
             startMain();

        // uploadAvatar("my_userid")
       //getuser("my_userid");
    }

    private void startMain() {
        Intent main = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(main);
    }

    private ApiInterface getInterfaceService() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        final ApiInterface mInterfaceService = retrofit.create(ApiInterface.class);
        return mInterfaceService;
    }

    private void loginProcessWithRetrofit(final String email, String password){
      ApiInterface mApiService = ApiInterface.retrofit.create(ApiInterface.class);
          Call mService=  mApiService.newsession(email,password);
        mService.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                Session mSession = response.body();
                if (mSession.token!="")
                {
                    settings =new Settings(getApplicationContext(), email, mSession.userid,mSession.token,null);
                    startMain();
                }else
                {
                    //  show error
                    failedLoginMessage.setText("Wrong username or password. Try again");
                    mEmailView.requestFocus();
                }

            }

            @Override
            public void onFailure(Call call, Throwable t) {
                call.cancel();
                Toast.makeText(LoginActivity.this, "Please check your network connection", Toast.LENGTH_LONG).show();
            }
        });
    }
    
    private void attemptLogin(){
        email=mEmailView.getText().toString();
        password=mPasswordView.getText().toString();
        loginProcessWithRetrofit(email,password);
    }
}


MainActivity
package com.bcg.loginexample;

public class MainActivity extends AppCompatActivity {
    private ImageButton imgAvatar;
    private de.hdodenhof.circleimageview.CircleImageView profile_image;

    private Button btnLogout, btnUseAvatar, btnTakePhoto;
    public Settings settings;
    MarshMallowPermission marshMallowPermission = new MarshMallowPermission(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        settings =new Settings(getApplicationContext()).getSettings();

        String email =settings.getEmail();
        TextView loginInformation = (TextView)findViewById(R.id.login_email);
        if(email != null || !email.equals("")  ){
            loginInformation.setText("Welcome!!! You have logged in as " + email);
        }else {
            loginInformation.setText("Your login email is missing");
        }
        profile_image =(de.hdodenhof.circleimageview.CircleImageView) findViewById(R.id.profile_image);

        imgAvatar = (ImageButton) findViewById(R.id.imgAvatar);
        imgAvatar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getImage();
            }
        });

        btnLogout = (Button) findViewById(R.id.btnLogout);
        btnLogout.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                settings.saveSettings(null,null,"",null);
                Toast.makeText(MainActivity.this, "Signing out! Token deleted.", Toast.LENGTH_LONG).show();

                Intent i = new Intent(MainActivity.this, LoginActivity.class);
                startActivity(i);
            }
        });

        btnUseAvatar = (Button) findViewById(R.id.btnUseAvatar);
        btnUseAvatar.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                String hash = MD5Util.md5Hex(settings.getEmail());
                String gravatarUrl = "http://www.gravatar.com/avatar/" + hash + "?s=600&d=600";
                settings.setAvatar_url(gravatarUrl);
                LoadImage();
            }
        });

        btnTakePhoto = (Button) findViewById(R.id.btnTakePhoto);
        btnTakePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getImage();
            }
        });

        getuser(settings.getUserid());
        //LoadImage( );
    }

    private void getImage() {
        if (!marshMallowPermission.checkPermissionForCamera()) {
            marshMallowPermission.requestPermissionForCamera();
        } else {
            if (!marshMallowPermission.checkPermissionForExternalStorage()) {
                marshMallowPermission.requestPermissionForExternalStorage();
            } else {
                Intent chooseImageIntent = ImagePicker.getPickImageIntent(getApplicationContext());
                startActivityForResult(chooseImageIntent, PICK_IMAGE_ID);
            }}
    }


    private void getuser(final String userId){
         ApiInterface mApiService = ApiInterface.retrofit.create(ApiInterface.class);
        Call mService= mApiService.getuser(userId);
       mService.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                User userObject = response.body();
                settings.setAvatar_url(ApiInterface.ENDPOINT +userObject.avatar_url.replaceFirst("^/", "") );
                LoadImage( );
                Toast.makeText(MainActivity.this, "Returned " + userObject, Toast.LENGTH_LONG).show();
             }

            @Override
            public void onFailure(Call call, Throwable t) {
                call.cancel();
                Toast.makeText(MainActivity.this, "Please check your network connection", Toast.LENGTH_LONG).show();
            }
        });
    }

    private static final int PICK_IMAGE_ID = 234; // the number doesn't matter
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch(requestCode) {
            case PICK_IMAGE_ID:
                Bitmap bitmap = ImagePicker.getImageFromResult(this, resultCode, data);

                File file = null;
                try {
                    file = savebitmap(bitmap, "pic.jpeg");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                uploadAvatar("my_userid", file );
                break;
            default:
                super.onActivityResult(requestCode, resultCode, data);
                break;
        }
    }
    public static File savebitmap(Bitmap bmp, String fName) throws IOException {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, 60, bytes);
        File f = new File(Environment.getExternalStorageDirectory()
                + File.separator + fName);
        f.createNewFile();
        FileOutputStream fo = new FileOutputStream(f);
        fo.write(bytes.toByteArray());
        fo.close();
        return f;
    }

    private void uploadAvatar(final String userid,File file){
        //   File file = new File("/storage/emulated/0/Android/data/com.bcg.loginexample/files/pic.jpg");
        // File file = new File("/storage/emulated/0/DCIM/camera/pic.jpg");
       // File file = new File(filePath);
        RequestBody body = RequestBody.create(MediaType.parse("image/*"), file);
        RequestBody description = RequestBody.create(MediaType.parse("text/plain"), "image");

        ApiInterface mApiService = ApiInterface.retrofit.create(ApiInterface.class);
        Call mService=  mApiService.postAvatar(body,description );
        mService.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                Avatar avatar = response.body();
                settings.setAvatar_url(ApiInterface.ENDPOINT  + avatar.avatar_url);

                LoadImage();
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                call.cancel();
                Toast.makeText(getApplicationContext(), "Error: " + t.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    }

    public void LoadImage() {
        String  url=settings.getAvatar_url();
         //load image from server if exists
        if (url!=""){
            Picasso.with(getApplicationContext())
                    .load(url)
                  .networkPolicy(NetworkPolicy.NO_CACHE)
                    .memoryPolicy(MemoryPolicy.NO_CACHE)
                    .placeholder(R.drawable.empty_avatar)
                    .noFade()
                    .into(profile_image);
        }

    }
 }

ApiInterface
package com.bcg.loginexample.Rest;


public interface ApiInterface {
    String ENDPOINT = "URL-TO-YOUR-REST-SERVICE/";

     @FormUrlEncoded
    @POST("sessions/new")
    Call newsession(@Field("email") String email, @Field("password")  String password);

    @GET("users/{userid}")
    Call getuser(@Path("userid") String userid);

    @Multipart
    @POST ("users/{userid}/avatar/{avatar}")
    Call postAvatar (@Part("image\"; filename=\"pic.jpg\" ") RequestBody file , @Part("FirstName") RequestBody description);

    public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ENDPOINT)
            .addConverterFactory(GsonConverterFactory.create())
           .build();
}

Source code available at Github

Points of Interest

Every modern application today consist of some back-end (REST) service and devices that are using it. NodeJS + Android Java is good example of how to build one.


Comments

Popular posts from this blog

Building REST API service that performs image labelling

Building ASP.NET Core app strengthened with AngularJS 2

Thing Translator