• MVC architecture

mvc

Mysql workbench 로 데이터 베이스 를 만들고, 클라이언트는 nodejs 를쓸것이다. 그리고 안드로이드 어플리케이션으로 창을 띄울것이다.

오늘 해볼것은 상품관리하는 프로그램이다 !

  • 파일구성은 밑그림과 같다. 상품관리어플

1. db 테이블 만들어두기

create table product(
   id int auto_increment primary key, 
   name varchar(100) not null, 
   price int, 
   image varchar(50)
);

2. nodejs 파일 만들기

product.js

var express = require('express');
var router = express.Router();
var db = require('../db');

/* 상품목록 */
router.get('/list', function(req, res, next) {
  var order =req.query.order;
  var strOrder='';
  var word = '%'+ req.query.word + '%';

  switch(order){
      case 'recently':
        strOrder='order by id desc';
        break;
      case 'low':
        strOrder='order by price';
        break;
      case 'high':
          strOrder='order by price desc';
          break;
      case 'name':
          strOrder='order by name';
          break;
  }
  var sql ='select * from product where name like ?' + strOrder;
  db.get().query(sql,[word], function(err, rows){
    res.send(rows);
  });
});

//상품정보
router.get('/read',function(req,res){
    var id = req.query.id;
    var sql='select * from product where id=?';
    db.get().query(sql, [id], function(err, rows){
        res.send(rows[0]);
    });
});

//상품등록페이지
router.get('/insert',function(req,res){
    res.render('insert');
});

//이미지 업로드
var multer = require('multer');
var upload = multer({
    storage:multer.diskStorage({
        destination:(req, file, done)=>{
            done(null, './public/images')
        },
        filename:(req, file, done) => {
            done(null, Date.now()+'_'+file.originalname);
        }
    })
});

//상품등록
router.post('/insert', upload.single('image'), function(req, res){
    var name = req.body.name;
    var price = req.body.price;
    var image ='';
    if(req.file !=null) image=req.file.filename;

    console.log(`name:${name}\nprice:${price}\nimage:${image}`);
    var sql ='insert into product(name,price,image) values(?,?,?)';
    db.get().query(sql, [name,price,image], function(err,rows){
        res.sendStatus(200);
    });
});

//상품삭제
var fs = require('fs');
router.post('/delete', function(req,res){
    var id = req.body.id;
    var image = req.body.image;
    var sql = 'delete from product where id=?';
    db.get().query(sql,[id], function(err,rows){
        res.sendStatus(200);
        //파일삭제
        if(image !=''){
            fs.unlink('./public/images/'+image, function(err){
                if(err) throw err;
            });
        }
    });
});

module.exports = router;

3. 안드로이드 스튜디오

InsertActivity

package com.example.ex13;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.io.File;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import static com.example.ex13.Remote.URL;

public class InsertActivity extends AppCompatActivity {
    ImageView image;
    String strImage="";
    EditText name, price;
    Retrofit retrofit;
    Remote remote;


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

        name=findViewById(R.id.name);
        price = findViewById(R.id.price);

        getSupportActionBar().setTitle("상품등록");
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        image= findViewById(R.id.image);
        image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder box = new AlertDialog.Builder(InsertActivity.this);
                box.setTitle("사진선택");
                box.setMessage("이미지 장소를 선택하세요!");
                box.setPositiveButton("사진촬영",null);
                box.setNegativeButton("앨범", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = new Intent(Intent.ACTION_PICK,
                                MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                        startActivityForResult(intent,0);
                    }
                });
                box.show();
            }
        });

        FloatingActionButton add = findViewById(R.id.add);
        add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String strName= name.getText().toString();
                final String strPrice = price.getText().toString();
                if(strName.equals("") || strPrice.equals("") || strImage.equals("")){
                    Toast.makeText(InsertActivity.this, "상품정보를입력하세요!", Toast.LENGTH_SHORT).show();
                }else {
                    AlertDialog.Builder box = new AlertDialog.Builder(InsertActivity.this);
                    box.setTitle("질의");
                    box.setTitle("상품정보를 등록하실래요?");
                    box.setPositiveButton("예", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //상품등록
                            RequestBody reqName = RequestBody.create(MediaType.parse("multipar/form-data"), strName);
                            RequestBody reqPrice =RequestBody.create(MediaType.parse("multipart/form-data"),strPrice);
                            File file =new File(strImage);
                            RequestBody reqImage = RequestBody.create(MediaType.parse("multipart/form-data"),file);
                            MultipartBody.Part partFile = MultipartBody.Part.createFormData("image", file.getName(), reqImage);
                            Call<ResponseBody> call = remote.insert(reqName, reqPrice, partFile);
                            call.enqueue(new Callback<ResponseBody>() {
                                @Override
                                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                                    finish();
                                }

                                @Override
                                public void onFailure(Call<ResponseBody> call, Throwable t) {
                                    Toast.makeText(InsertActivity.this, "상품이 등록되었습니다.",Toast.LENGTH_SHORT).show();
                                    finish();
                                }
                            });
                        }
                    });
                    box.setNegativeButton("아니오",null);
                    box.show();
                }
            }
        });

        retrofit=new Retrofit.Builder()
                .baseUrl(URL).addConverterFactory(GsonConverterFactory.create()).build();
        remote=retrofit.create(Remote.class);
    }

    //이미지를 선택한 경우
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(requestCode==0){//앨범에서 돌아온 경우
            //image.setImageURI(data.getData());
            Cursor cursor =getContentResolver().query(data.getData(),null,null,null,null);
            cursor.moveToFirst();
            strImage=cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            System.out.println("....."+strImage);
            Bitmap bitmap = BitmapFactory.decodeFile(strImage);
            image.setImageBitmap(bitmap);

        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
       switch (item.getItemId()){
           case android.R.id.home:
               finish();
       }
        return super.onOptionsItemSelected(item);
    }
}

MainActivity

package com.example.ex13;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.squareup.picasso.Picasso;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import static com.example.ex13.Remote.URL;

public class MainActivity extends AppCompatActivity {
    Remote remote;
    Retrofit retrofit;
    List<ProductVO> array = new ArrayList<>();
    ProductVO vo = new ProductVO();
    ProductAdapter productAdapter = new ProductAdapter();
    String order ="recently";
    String strQuery="";

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

        getSupportActionBar().setTitle("상품관리");
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_home);

        retrofit=new Retrofit.Builder()
                .baseUrl(URL).addConverterFactory(GsonConverterFactory.create()).build();
        remote=retrofit.create(Remote.class);
        callList();

        RecyclerView list =findViewById(R.id.list);
        list.setAdapter(productAdapter);
        list.setLayoutManager(new LinearLayoutManager(this));

        FloatingActionButton add = findViewById(R.id.add);
        add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, InsertActivity.class);
                startActivity(intent);
            }
        });
    }

    public void callList(){
        Call<List<ProductVO>> call = remote.list(order,strQuery);
        call.enqueue(new Callback<List<ProductVO>>() {
            @Override
            public void onResponse(Call<List<ProductVO>> call, Response<List<ProductVO>> response) {
                array=response.body();
                productAdapter.notifyDataSetChanged();
            }

            @Override
            public void onFailure(Call<List<ProductVO>> call, Throwable t) {

            }
        });
    }

    //상품어댑터
    class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ViewHolder>{

        @NonNull
        @Override
        public ProductAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = getLayoutInflater().inflate(R.layout.item,parent, false);
            return new ViewHolder(view);
        }

        @Override
        public void onBindViewHolder(@NonNull ProductAdapter.ViewHolder holder, int position) {

            final ProductVO vo =array.get(position);
            holder.name.setText(vo.getName());
            DecimalFormat df = new DecimalFormat("#,####원");
            holder.price.setText(df.format(vo.getPrice()));
            Picasso.with(MainActivity.this)
                    .load(URL + "images/"+vo.getImage())
                    .into(holder.image);
            //아이템을 클릭한 경우
            holder.item.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this, ReadActivity.class);
                    intent.putExtra("id", vo.getId());
                    startActivity(intent);
                }
            });
        }

        @Override
        public int getItemCount() {
            return array.size();
        }

        public class ViewHolder extends RecyclerView.ViewHolder {
            ImageView image;
            TextView name, price;
            CardView item;
            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                image=itemView.findViewById(R.id.image);
                name=itemView.findViewById(R.id.name);
                price=itemView.findViewById(R.id.price);
                item=itemView.findViewById(R.id.item);

            }
        }
    }

    //권한체크
    public void checkPermission() {
        String[] permissions= { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA };
        ArrayList<String> noPermissions=new ArrayList<>();
        for(String permission:permissions){
            if(ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                noPermissions.add(permission);
            }
        }
        if(noPermissions.size() > 0){
            String[] reqPermissions=noPermissions.toArray(new String[noPermissions.size()]);
            ActivityCompat.requestPermissions(this, reqPermissions, 100);
        }
    }

    @Override
    protected void onRestart() {

        super.onRestart();
        callList();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        //검색어를 입력한경우
        MenuItem search=menu.findItem(R.id.search);
        SearchView searchView=(SearchView)search.getActionView();
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                strQuery=newText;
                callList();
                return false;
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.recently:
                order="recently";
                break;
            case R.id.high:
                order="high";
                break;
            case R.id.low:
                order="low";
                break;
            case R.id.name:
                order="name";
                break;
        }
        callList();
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        switch (order){
            case "recently":
                menu.findItem(R.id.recently).setChecked(true);
                break;
            case "high":
                menu.findItem(R.id.high).setChecked(true);
                break;
            case "low":
                menu.findItem(R.id.low).setChecked(true);
                break;
            case "name":
                menu.findItem(R.id.name).setChecked(true);
                break;
        }
        return super.onPrepareOptionsMenu(menu);
    }
}

ProductVO (생략)

ReadActivity

package com.example.ex13;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.ImageView;

import com.squareup.picasso.Picasso;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import static com.example.ex13.Remote.URL;

public class ReadActivity extends AppCompatActivity {
    int id;
    Remote remote;
    Retrofit retrofit;
    ImageView image;
    EditText name, price;
    ProductVO vo;

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

        image = findViewById(R.id.image);
        name = findViewById(R.id.name);
        price = findViewById(R.id.price);

        Intent intent = getIntent();
        id = intent.getIntExtra("id", 0);

        getSupportActionBar().setTitle("상품 정보 | "+id);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);


        retrofit=new Retrofit.Builder()
                .baseUrl(URL).addConverterFactory(GsonConverterFactory.create()).build();
        remote=retrofit.create(Remote.class);
        Call<ProductVO> call = remote.read(id);
        call.enqueue(new Callback<ProductVO>() {
            @Override
            public void onResponse(Call<ProductVO> call, Response<ProductVO> response) {
                vo = response.body();
                name.setText(vo.getName());
                price.setText(String.valueOf(vo.getPrice()));

                Picasso.with(ReadActivity.this)
                        .load(URL+"images/"+vo.getImage())
                        .into(image);

            }

            @Override
            public void onFailure(Call<ProductVO> call, Throwable t) {

            }
        });

    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                break;
            case R.id.delete:
                AlertDialog.Builder box = new AlertDialog.Builder(this);
                box.setTitle("질의");
                box.setMessage("상품정보를 삭제하시겠습니까?");
                box.setPositiveButton("예", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Call<Void> call =remote.delete(vo);
                        call.enqueue(new Callback<Void>() {
                            @Override
                            public void onResponse(Call<Void> call, Response<Void> response) {
                                finish();
                            }

                            @Override
                            public void onFailure(Call<Void> call, Throwable t) {

                            }
                        });
                    }
                });
                box.setNegativeButton("아니오",null);
                box.show();
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.read,menu);
        return super.onCreateOptionsMenu(menu);
    }
}

Remote.interface

package com.example.ex13;

import java.util.List;

import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.Query;

public interface Remote {

    public static final String URL="http://10.0.2.2:3000/";

    @GET("product/list")
    Call<List<ProductVO>> list(@Query("order") String order, @Query("word") String word);

    @GET("product/read")
    Call<ProductVO> read(@Query("id") int id);

    @Multipart
    @POST("product/insert")
    Call<ResponseBody> insert(
        @Part("name") RequestBody reqName,
        @Part("price") RequestBody reqPrice,
        @Part MultipartBody.Part partFile
    );

    @POST("product/delete")
    Call<Void> delete(@Body ProductVO vo);
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/add"
        android:src="@drawable/ic_add"
        android:backgroundTint="#E2D0F9"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="20sp"
        app:borderWidth="0sp"/>


</RelativeLayout>

activity_read

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ReadActivity"
    android:gravity="center"
    android:padding="30sp">
    <ImageView
        android:id="@+id/image"
        android:layout_width="300sp"
        android:layout_height="250sp"
        android:src="@mipmap/ic_launcher"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10sp"/>
    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="상품이름"
        android:layout_below="@+id/image"
        android:fontFamily="@font/kang"
        android:layout_marginBottom="20sp"/>
    <EditText
        android:id="@+id/price"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="상품가격"
        android:layout_below="@+id/name"
        android:fontFamily="@font/kang"
        android:layout_marginBottom="20sp"/>
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/add"
        android:src="@drawable/ic_add"
        android:layout_alignParentBottom="true"
        app:backgroundTint="#E2D0F9"
        android:layout_alignParentRight="true"
        android:layout_margin="10sp"
        app:borderWidth="0sp"/>

</RelativeLayout>

레이아웃 설정할때 아이디 지정을 잘해주자!!

메인페이지

-> 메인페이지 모습이다. db에서 정보를 가져왔다. 스크롤을 내리면 밑에 목록을 더볼수있다.


정렬방식

-> 정렬방식은 네가지로 설정해두었다. 체크하면 정렬방식이 바뀐다. 사진은 낮은가격의 정렬이다.


상품등록어플

-> 이미지를 선택해서 핸드폰안에있는 사진을 불러올수있다. 상품이름과 상품가격을 입력해서 새로운상품등록이 가능하다. 상품정보를 입력하지않으면 입력하라는 경고 메세지 창이뜬다.


상품정보어플

-> 메인페이지에서 해당상품을 누르면 상품정보를 볼수있다. 잘불러와짐


검색어플

-> 침대라는 단어로 검색하면 나오는 목록이다. 검색기능또한 가능하다.

댓글남기기