Android- 상품관리
- MVC architecture
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에서 정보를 가져왔다. 스크롤을 내리면 밑에 목록을 더볼수있다.
-> 정렬방식은 네가지로 설정해두었다. 체크하면 정렬방식이 바뀐다. 사진은 낮은가격의 정렬이다.
-> 이미지를 선택해서 핸드폰안에있는 사진을 불러올수있다. 상품이름과 상품가격을 입력해서 새로운상품등록이 가능하다. 상품정보를 입력하지않으면 입력하라는 경고 메세지 창이뜬다.
-> 메인페이지에서 해당상품을 누르면 상품정보를 볼수있다. 잘불러와짐
-> 침대라는 단어로 검색하면 나오는 목록이다. 검색기능또한 가능하다.
댓글남기기