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.


Building ASP.NET Core app strengthened with AngularJS 2

Article Source

Introduction

In this tutorial I will show you how to create Master-Detail web application that also have a search option. We will use the data of the Cruising Company. It contains data about: ships, booking info, sale units etc.

Using the code

The application consist of two parts:
  • Backend: RESTful API written in .NET Core
  • Frontend: SPA written in Angular 2
Download and install following tools and libraries: The source code is located on Github. For simplicity reasons here I am going to highlight only the important parts of the code.

Start by creating new project: New -> Project. Select Web -> ASP.NET Core Angular 2 Starter.


Replace following files with the appropriate code. project.json
{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type": "platform"
    },
    "Microsoft.AspNetCore.AngularServices": "1.0.0-*",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview2-final",
      "type": "build"
    },
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },

  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
    "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "appsettings.json",
      "ClientApp/dist",
      "node_modules",
      "Views",
      "web.config",
      "wwwroot"
    ]
  },

  "scripts": {
    "prepublish": [
      "npm install",
      "node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod",
      "node node_modules/webpack/bin/webpack.js --env.prod"
    ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  },

  "tooling": {
    "defaultNamespace": "CruiseCompanyApp"
  }
}
package.json
{
  "name": "Angular2Spa",
  "version": "0.0.0",
  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/platform-server": "2.0.0",
    "@angular/router": "3.0.0",
    "@types/node": "^6.0.38",
    "angular2-platform-node": "~2.0.10",
    "angular2-universal": "~2.0.10",
    "angular2-universal-polyfills": "~2.0.10",
    "aspnet-prerendering": "^1.0.6",
    "aspnet-webpack": "^1.0.11",
    "bootstrap": "^3.3.7",
    "css": "^2.2.1",
    "css-loader": "^0.25.0",
    "es6-shim": "^0.35.1",
    "expose-loader": "^0.7.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^2.2.1",
    "preboot": "^4.5.2",
    "raw-loader": "^0.5.1",
    "rxjs": "5.0.0-beta.12",
    "style-loader": "^0.13.0",
    "to-string-loader": "^1.1.5",
    "ts-loader": "^0.8.2",
    "typescript": "^2.0.0",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.14",
    "webpack-externals-plugin": "^1.0.0",
    "webpack-hot-middleware": "^2.10.0",
    "webpack-merge": "^0.14.1",
    "zone.js": "^0.6.21"
  }
}

Now rebuild project so all dependencies are installed. Add Models folder in the root of your project and add those 2 files: JSONModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace A2SPA.Models {
  public class SalesUnit {
    [Key]
    public int id { get; set; }
    public string name { get; set; }
    public string country { get; set; }
    public string currency { get; set; }
    }

  public class Ship {
    [Key]
    public int id { get; set; }
    public int salesUnitId { get; set; }
    public string name { get; set; }
    }

  public class Booking {
    [Key]
    public int id { get; set; }
    public int shipId { get; set; }
    public string bookingDate { get; set; }
    public float price { get; set; }
    }

  public class JSONModel {
    public List salesUnits { get; set; }
    public List ships { get; set; }
    public List bookings { get; set; }
    }
  }

ViewModels.cs
using System.ComponentModel.DataAnnotations;

namespace A2SPA.Models {
  //List
  public class SearchView {
    [Key]
    public int BookingId { get; set; }
    public string ShipName { get; set; }
    public string BookingDate { get; set; }
    public float Price { get; set; }
    }

  //List
  public class SalesView {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public float Total { get; set; }
    public string Currency { get; set; }
    }

  //Detail
  public class SalesDetailView {
    public int BookingId { get; set; }
    public string ShipName { get; set; }
    public float Price { get; set; }
    public string Currency { get; set; }
    }

  }

Add Api folder in the root of your project and place this file inside:
BookingsAPI.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
using A2SPA.Models;

namespace CruiseCompanyApp.Controllers {
  [Produces("application/json")]
  [Route("api")]
  public class BookingsAPI : Controller {
    private IHostingEnvironment _env;
    public JSONModel jsonData;
    public BookingsAPI (IHostingEnvironment env) {
      _env = env;
      ReadJSON();
      }

    //Main list
    [Route("bookings/{quarter}")]
    public List Bookings (int quarter) {
      var quarterBookings = BookingsByQuarter(quarter);

      var subQuery1 = from qb in quarterBookings
                      group qb by qb.shipId into shipBookings
                      select new {
                        ShipKey = shipBookings.Key,
                        Sum = shipBookings.Sum(p => p.price)
                        };

      var shipSum = from emp in jsonData.ships
                    join s in subQuery1 on emp.id equals s.ShipKey
                    join pd in jsonData.salesUnits on emp.salesUnitId equals pd.id
                    select new SalesView {
                      Id = pd.id,
                      Name = pd.name,
                      Country = pd.country,
                      Total = s.Sum,
                      Currency = pd.currency
                      };

      var subQuery2 = from s in shipSum
                      group s by s.Country
                        into res
                      select new SalesView {
                        Id = res.FirstOrDefault().Id,
                        Name = res.FirstOrDefault().Name,
                        Country = res.FirstOrDefault().Country,
                        Total = (float)Math.Round(res.Sum(r => r.Total)),
                        Currency = res.FirstOrDefault().Currency
                        };

      var salesViewList = subQuery2.OrderByDescending(c => c.Total).ToList();
      return salesViewList;
      }

    //Details
    [Route("bookings/{saleUnit}/{quarter}")]
    public List BookingsBySaleUnit (int saleUnit, int quarter) {
      string currency = jsonData.salesUnits.Where(u => u.id == saleUnit).FirstOrDefault().currency;
      var quarterBookings = BookingsByQuarter(quarter);
      var shipsBySaleUnit = from s in jsonData.ships
                            where s.salesUnitId == saleUnit
                            select s;

      var shipSum = (from b in quarterBookings
                     join s in shipsBySaleUnit on b.shipId equals s.id
                     select new SalesDetailView {
                       BookingId = b.id,
                       ShipName = s.name,
                       Price = b.price,
                       Currency = currency
                       }).ToList();

      return shipSum;
      }

    // Filter by bookingId or  shipname 
    [Route("searchbookings/{searchValue}")]
    public List SearchBookings (string searchValue) {
      IEnumerable searchResults = null;
      IEnumerable bookings = null;
      int id;
      bool isNumeric = int.TryParse(searchValue, out id);


      if (isNumeric)
        bookings = jsonData.bookings.Where(b => b.id == id);

      else {
        var getShipIds = jsonData.ships.Where(s => s.name.ToLower().StartsWith(searchValue.ToLower()));
        bookings = from booking in jsonData.bookings where getShipIds.Select(s => s.id).Contains(booking.shipId) select booking;
        }
      searchResults = from ship in jsonData.ships
                      join b in bookings on ship.id equals b.shipId
                      select new SearchView {
                        BookingId = b.id,
                        ShipName = ship.name,
                        BookingDate = DateTime.Parse(b.bookingDate).ToString("yyyy-MM-dd"),
                        Price = b.price
                        };
      return searchResults.ToList();
      }

    // Filter bookisng by quarter (0 for all)
    public List BookingsByQuarter (int quarter) {
      //  ReadJSON();
      int fromMonth = quarter * 3 - 3;
      int toMonth = quarter * 3;
      List res = null;

      if (quarter == 0)
        res = jsonData.bookings.ToList();
      else {
        res = (from b in jsonData.bookings
               where
                         Convert.ToDateTime(b.bookingDate).Year == 2016 &&
                         Convert.ToDateTime(b.bookingDate).Month > fromMonth &&
                         Convert.ToDateTime(b.bookingDate).Month <= toMonth
               select b).ToList();
        }
      return res;
      }

    private void ReadJSON () {
      if (jsonData != null) return;
      var jsonFile = _env.ContentRootPath + Path.DirectorySeparatorChar.ToString() + "Data" +
                                                               Path.DirectorySeparatorChar.ToString() + "TrialDayData.json";
      string fileContent;
      using (var stream = new FileStream(jsonFile, FileMode.Open))
      using (StreamReader sr = new StreamReader(stream)) {
        fileContent = sr.ReadToEnd();
        }
      jsonData = JsonConvert.DeserializeObject(fileContent);
      }

    }
  }

Add Controllers folder in the root of your project and place this file inside:
HomeController
using Microsoft.AspNetCore.Mvc;

namespace CruiseCompanyApp.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

Data folder should contain file TrialDayData.json which you will find on Github

Now we should be able to test the application API call. Hit Ctrl-F5 and navigate to http://localhost:64880/api/bookings/1
[{"id":8,"name":"soyoulun.com","country":"China","total":961880.0,"currency":"¥"},
{"id":1,"name":"dreamlines.de","country":"Germany","total":906369.0,"currency":"€"},
{"id":3,"name":"dreamlines.it","country":"Italy","total":831574.0,"currency":"€"},
{"id":7,"name":"dreamlines.nl","country":"Netherlands","total":793538.0,"currency":"€"},
{"id":5,"name":"dreamlines.com.au","country":"Australia","total":778521.0,"currency":"AU$"},
{"id":2,"name":"dreamlines.com.br","country":"Brazil","total":753164.0,"currency":"R$"},
{"id":6,"name":"dreamlines.ru","country":"Russia","total":632055.0,"currency":"RUB"},
{"id":4,"name":"dreamlines.fr","country":"France","total":469810.0,"currency":"€"}]

Now it is still left to do the front-end part. For this task I suggest you to copy folders from GitHub and place it in your solution. In the end, your solution tree should look like this

Source code available at Github
and
Live demo available here



Points of Interest


ASP.NET Core is a powerful platform, though it has not yet been completed as ASP.NET MVC. Yet combined with the AngularJS framework, it gives excellent results.


Thing Translator

Article Source

Introduction

In Part I of my tutorial we built a Web API that handles image labeling and translation into different languages. Now we will build Android app that can consume that API. Features we are going to have are:
  • select destination language
  • select image from gallery or take new photo using camera
  • option to toogle speech output on/off

Using the code

All the code base is located in code section of this article. For simplicity reasons here I am going to highlight only the important parts of the code.
build.gradle
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.code.gson:gson:2.6.1'
    compile 'com.android.support:support-v13:23.4.0'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile 'com.squareup.retrofit2:retrofit:2.0.0'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha4'
    testCompile 'junit:junit:4.12'
    compile 'com.pixplicity.easyprefs:library:1.8.1@aar'
    compile 'com.jakewharton:butterknife:8.4.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
}
We will use some really cool Java libraries that makes Android development lot easier.
  • easyprefs -  easy access to SharedPrefferences
  • butterknife - makes Views declaration lot easier
  • retrofit - enables us to work with REST API's quickly and easily 
activity_main - spinner View has a custom layout defined in spinner_item.xml
<Spinner
    android:id="@+id/spinner"
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:layout_marginBottom="16dp"
    android:layout_weight="0.91"
    android:gravity="bottom|right" />
<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="30dp"
    android:textColor="@android:color/darker_gray"
    android:textSize="24dp"
    />
ApiInterface.java - this is interface for our REST API calls. Put your API url we build in previous tutorial into ENDPOINT variable. As you can see only 2 parameters are used when making POST request:
  1. Image file
  2. Language Code
package thingtranslator2.jalle.com.thingtranslator2.Rest;
 

public interface ApiInterface {
    
    String ENDPOINT = "YOUR-API-URL/api/";

    @Multipart
    @POST("upload")
    Call<translation> upload(@Part("image\"; filename=\"pic.jpg\" ") RequestBody file, @Part("FirstName") RequestBody langCode1);

    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .readTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60, TimeUnit.SECONDS)
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ENDPOINT)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
}

Translation class
package thingtranslator2.jalle.com.thingtranslator2.Rest;

public class Translation {
    public String Original;
    public String Translation;
    public String Error;
}
Tools - ImagePicker this class is used for getting image from gallery of taking new photo from camera. Tools - MarshMallowPermission this is used for setting up camera permissions on Android 6.
AndroidManifest
    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="thingtranslator2.jalle.com.thingtranslator2">

    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity" >

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
MainActivity.java
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
    public
    @BindView(R.id.txtTranslation)
    TextView txtTranslation;
    public
    @BindView(R.id.spinner)
    Spinner spinner;
    public
    @BindView(R.id.imgPhoto)
    ImageButton imgPhoto;
    public
    @BindView(R.id.imgSpeaker)
    ImageButton btnSpeaker;

    public Boolean speakerOn, firstRun;
    public TextToSpeech tts;
    public List<string> languages = new ArrayList<string>();
    public ArrayList<language> languageList = new ArrayList<language>();
    public ArrayAdapter<language> spinnerArrayAdapter;
    public Language selectedLanguage;
    public static final int PICK_IMAGE_ID = 234; // the number doesn't matter
    thingtranslator2.jalle.com.thingtranslator2.MarshMallowPermission marshMallowPermission = new thingtranslator2.jalle.com.thingtranslator2.MarshMallowPermission(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        ButterKnife.bind(this);

        //init our Shared preferences helper
        new Prefs.Builder()
                .setContext(this)
                .setMode(ContextWrapper.MODE_PRIVATE)
                .setPrefsName(getPackageName())
                .setUseDefaultSharedPreference(true)
                .build();

        //  Prefs.clear();
        firstRun = Prefs.getBoolean("firstRun", true);
        spinner.setOnItemSelectedListener(this);
        tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status != TextToSpeech.ERROR) {
                    fillSpinner();


                }
            }
        });


        if (firstRun) {
            Prefs.putBoolean("firstRun", false);
            Prefs.putBoolean("speakerOn", true);
            Prefs.putString("langCode", "bs");
        }
        setSpeaker();
    }

    private void setSpeaker() {
        int id = Prefs.getBoolean("speakerOn", false) ? R.drawable.ic_volume : R.drawable.ic_volume_off;
        btnSpeaker.setImageBitmap(BitmapFactory.decodeResource(getResources(), id));
    }

    public void ToogleSpeaker(View v) {
        Prefs.putBoolean("speakerOn", !Prefs.getBoolean("speakerOn", false));
        setSpeaker();
        Toast.makeText(getApplicationContext(), "Speech " + (Prefs.getBoolean("speakerOn", true) ? "On" : "Off"), Toast.LENGTH_SHORT);
    }

    private void fillSpinner() {
        Iterator itr = tts.getAvailableLanguages().iterator();
        while (itr.hasNext()) {
            Locale item = (Locale) itr.next();
            languageList.add(new Language(item.getDisplayName(), item.getLanguage()));
        }
        //Sort that array
        Collections.sort(languageList, new Comparator<language>() {
            @Override
            public int compare(Language o1, Language o2) {
                return o1.getlangCode().compareTo(o2.getlangCode());
            }
        });

        spinnerArrayAdapter = new ArrayAdapter<language>(this, R.layout.spinner_item, languageList);
        spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(spinnerArrayAdapter);

        //Set the default language to bs- Bosnian
        String def = Prefs.getString("langCode", "bs");
        for (Language lang : languageList) {
            if (lang.getlangCode().equals(def)) {
                spinner.setSelection(languageList.indexOf(lang));
            }
        }
    }

    ;

    //Get image from  Gallery or Camera
    public void getImage(View v) {
        if (!marshMallowPermission.checkPermissionForCamera()) {
            marshMallowPermission.requestPermissionForCamera();
        } else {
            if (!marshMallowPermission.checkPermissionForExternalStorage()) {
                marshMallowPermission.requestPermissionForExternalStorage();
            } else {
                Intent chooseImageIntent = thingtranslator2.jalle.com.thingtranslator2.ImagePicker.getPickImageIntent(getApplicationContext());
                startActivityForResult(chooseImageIntent, PICK_IMAGE_ID);
            }
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (data == null) return;

        switch (requestCode) {
            case PICK_IMAGE_ID:
                Bitmap bitmap = thingtranslator2.jalle.com.thingtranslator2.ImagePicker.getImageFromResult(this, resultCode, data);
                imgPhoto.setImageBitmap(null);
                imgPhoto.setBackground(null);
                imgPhoto.setImageBitmap(bitmap);
                imgPhoto.invalidate();
                imgPhoto.postInvalidate();

                File file = null;
                try {
                    file = savebitmap(bitmap, "pic.jpeg");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                uploadImage(file);
                break;
            default:
                super.onActivityResult(requestCode, resultCode, data);
                break;
        }
    }

    private void uploadImage(File file) {
        RequestBody body = RequestBody.create(MediaType.parse("image/*"), file);
        RequestBody langCode1 = RequestBody.create(MediaType.parse("text/plain"), selectedLanguage.langCode);

        final ProgressDialog progress = new ProgressDialog(this);
        progress.setMessage("Processing image...");
        progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        progress.setIndeterminate(true);
        progress.show();


        ApiInterface mApiService = ApiInterface.retrofit.create(ApiInterface.class);
        Call<translation> mService = mApiService.upload(body, langCode1);
        mService.enqueue(new Callback<translation>() {
            @Override
            public void onResponse(Call<translation> call, Response<translation> response) {
                progress.hide();
                Translation result = response.body();
                txtTranslation.setText(result.Translation);
                txtTranslation.invalidate();

                if (Prefs.getBoolean("speakerOn", true)) {
                    tts.speak(result.Translation, TextToSpeech.QUEUE_FLUSH, null);
                }
            }

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


    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;
    }


    @Override
    public void onItemSelected(AdapterView parent, View view, int position, long id) {

        selectedLanguage = (Language) spinner.getSelectedItem();
        //  langCode = languages.get(position);
        tts.setLanguage(Locale.forLanguageTag(selectedLanguage.langCode));
        Prefs.putString("langCode", selectedLanguage.langCode);
    }

    @Override
    public void onNothingSelected(AdapterView parent) {

    }
}
The end result of this tutorial is an application that looks like this. Remember your WebAPI service needs to be up and running in order for all this to work.

Points of Interest

There are many ways for  improvement and upgrading this application. One of my ideas is that the image labelling could be done during the live preview on your camera. Having the preview window opened, we can invoke the API every 5-10 seconds and have live labelling about what we see through our camera.
One drawback is that  it would consume a lot of bandwidth and also remember, Google API Vision and most of the other APIs are not free. You pay for the API usage when requests exceed the allowed quota.
Source code available at Github

Building REST API service that performs image labelling

Article Source

Introduction

For this tutorial I will demonstrate how to use powerful Google API's for making some useful applications. This tutorial is divided into two parts:
Part 1. Building WebAPI service that handles image labeling and translation into different languages.
Part 2. Consuming this RESTful service from Android application.

Using the code

We will start by creating new WebAPI project. Start Visual Studio select: New project ->  C# ->Web ->ASP.NET Web Application -Empty. Check WebAPI, and host in the cloud to be able to publish this project later.
packages.config will contain all the libraries we need for this project.
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
  <package id="Google.Apis" version="1.19.0" targetFramework="net45" />
  <package id="Google.Apis.Auth" version="1.19.0" targetFramework="net45" />
  <package id="Google.Apis.Core" version="1.19.0" targetFramework="net45" />
  <package id="Google.Apis.Translate.v2" version="1.19.0.543" targetFramework="net45" />
  <package id="Google.Apis.Vision.v1" version="1.19.0.683" targetFramework="net45" />
  <package id="GoogleApi" version="2.0.13" targetFramework="net45" />
  <package id="log4net" version="2.0.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net45" developmentDependency="true" />
  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
  <package id="Zlib.Portable.Signed" version="1.11.0" targetFramework="net45" />
</packages>

Setting up Api Keys

Since we will be using Google APIs we need to set up a google cloud vision api project first.
1. For Google Vision API  download  VisionAPI-xxxxxx.json file and save it in your project root directory
2. For Translation API get the API key from same page
Back in the code we will first invoke those API variables. Replace values with they keys acquired above.
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Web.Http;

namespace ThingTranslatorAPI2 {
  public class Global : System.Web.HttpApplication {

    public static String apiKey;
    protected void Application_Start() {
      GlobalConfiguration.Configure(WebApiConfig.Register);

      apiKey = "API-KEY";

      createEnvVar();
    }

    private static void createEnvVar() {
      var GAC = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS");

        if (GAC == null) {
        var VisionApiKey = ConfigurationManager.AppSettings["VisionApiKey"]; 
        if (VisionApiKey != null) {
          var path = System.Web.Hosting.HostingEnvironment.MapPath("~/") + "YOUR-API-KEY.json";

          Trace.TraceError("path: " + path);

          File.WriteAllText(path,VisionApiKey );
          Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", path);
        }
      }
    }
  }
}

WebApiConfig located in Ap_Start folder will contain this. We tell the server to handle routes using attribut routing and not by the default router config.

using System.Web.Http;
namespace ThingTranslatorAPI2
{
    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();
        }
    }
}

API Controller

We need an API controller that will handle requests and process them. Request should contain image file and language code for the language we want translation to be made. Images will be processed in memory so no need to save it on the disc.
TranslatorController.cs
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Results;
using GoogleApi;
using GoogleApi.Entities.Translate.Translate.Request;
using TranslationsResource = Google.Apis.Translate.v2.Data.TranslationsResource;

namespace ThingTranslatorAPI2.Controllers {
   
  [RoutePrefix("api")]
  public class TranslatorController : ApiController
  {

    [Route("upload")]
    [HttpPost]
    public async Task<jsonresult<response>> Upload() {
      if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);

      String langCode = string.Empty;
      var response = new Response();
      byte[] buffer = null;

      var provider = new MultipartMemoryStreamProvider();
      await Request.Content.ReadAsMultipartAsync(provider);

      foreach (var content in provider.Contents)
      {
        if (content.Headers.ContentType !=null && content.Headers.ContentType.MediaType.Contains("image"))
           buffer = await content.ReadAsByteArrayAsync();
        else
          langCode = await content.ReadAsStringAsync();
      }

      var labels = LabelDetectior.GetLabels(buffer);
      
      try {
        //Take the first label  that has the best match
        var bestMatch = labels[0].LabelAnnotations.FirstOrDefault()?.Description;
        String translateText;
        if (langCode == "en")
          translateText = bestMatch;
        else
          translateText = TranslateText(bestMatch, "en", langCode);

        //original is our text in English
        response.Original = bestMatch;
        response.Translation = translateText;

      } catch (Exception ex) {
        response.Error = ex.Message;
        return Json(response);
      }

      return Json(response);
    }

   //Translate text from source to target language
    private String TranslateText(String text, String source, String target) {

      var _request = new TranslateRequest {
        Source = source,
        Target = target,
        Qs = new[] { text },
        Key = Global.apiKey
      };

      try {
        var _result = GoogleTranslate.Translate.Query(_request);
        return _result.Data.Translations.First().TranslatedText;
      } catch (Exception ex) {
        return ex.Message;
      }
    }
 }
}
For image labeling we need this class
LabelDetect or.cs
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Vision.v1;
using Google.Apis.Vision.v1.Data;
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ThingTranslatorAPI2 {
  public class LabelDetectior {
    
   // Get labels from image in memory
    public static IList<AnnotateImageResponse> GetLabels(byte[] imageArray) {
      try
      {
        VisionService vision = CreateAuthorizedClient();
        // Convert image to Base64 encoded for JSON ASCII text based request   
        string imageContent = Convert.ToBase64String(imageArray);
        // Post label detection request to the Vision API
        var responses = vision.Images.Annotate(
            new BatchAnnotateImagesRequest() {
              Requests = new[] {
                    new AnnotateImageRequest() {
                        Features = new [] { new Feature() { Type = "LABEL_DETECTION"}},
                        Image = new Image() { Content = imageContent }
                    }
           }
            }).Execute();
        return responses.Responses;
      }
      catch (Exception ex)
      {
        Trace.TraceError(ex.StackTrace);
      }
      return null;
    }

    // returns an authorized Cloud Vision client. 
    public static VisionService CreateAuthorizedClient() {
      try {
        GoogleCredential credential = GoogleCredential.GetApplicationDefaultAsync().Result;
        // Inject the Cloud Vision scopes
        if (credential.IsCreateScopedRequired) {
          credential = credential.CreateScoped(new[]
          {
                    VisionService.Scope.CloudPlatform
                });
        }
        return new VisionService(new BaseClientService.Initializer {
          HttpClientInitializer = credential,
          GZipEnabled = false
        });
      } catch (Exception ex) {
        Trace.TraceError("CreateAuthorizedClient: " + ex.StackTrace);
      }
      return null;
    }
  }
}<annotateimageresponse>
Response.cs will look like this
namespace ThingTranslatorAPI2.Controllers
{
  public class Response
  {
    public string Original { get; set; }
    public string Translation { get; set; }
    public string Error { get; set; }
  }
}
If you have any problem compiling the code check the source code attached here.

Now let's publish this to Azure Cloud. Go to Build - Publish and fill in all 4 inpout boxes to match your Azure settings.

Now we can use Postman to test it.


Response
{"Original":"mouse","Translation":"miš","Error":null}

We received response that contain image label in english and translated version for the language we specified with langCode parameter..

Points of Interest

There are few API's available today that can handle image labeling. One of them which I found very exotic is called CloudSight. Although it is more accurate then others it relies on human tagging. The downside of this is that it takes more time than a machine to do the job. Usually, response is received after 10-30 seconds.
I can imagine if we run our app and the timeout happens. How could we call it connection timeout or maybe coffee break timeout ; -) ?
That's all in this tutorial.  In next article we will build Thing Translator app that consumes this API.
Source code available at Github