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 ASP.NET Core app strengthened with AngularJS 2

Thing Translator