본문 바로가기
AndroidStudio

웹 서비스 HTTP 통신 : 글씨랑 이미지 둘 다 가져오기*

by EUN-JI 2023. 9. 6.

EX69

퍼미션하기

android:usesCleartextTraffic="true"
<uses-permission android:name="android.permission.INTERNET"/>

gradle>viewbinding하기

buildFeatures {
    viewBinding true
// 5개하면 됨. 시작부터
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.github.bumptech.glide:glide:4.16.0'
//새로고침기능XML에서 사용
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
//

05Retrofit> insetDB.php

<?php
    header('Content-Type:text/plain; charset=utf-8');

    //@PartMap으로 전달된 POST방식의 데이터들
    $name= $_POST['name'];
    $title= $_POST['title'];
    $message=$_POST['msg'];
    $price=$_POST['price'];

    //@Part로 전달된 이미지파일
    $file= $_FILES['img'];
    $srcName= $file['name'];   //원본파일명
    $tmpName=$file['tmp_name'];  //임시저장소 위치

    $dstName="./image/IMG_" . date('YmdHis') .$srcName;
    move_uploaded_file($tmpName,$dstName);

    //데이터가 잘 왔는지 확인.
    // echo $name . "<br>";
    // echo $title . "<br>";
    // echo $message . "<br>";
    // echo $price . "<br>";
    // echo $tmpName . "<br>";
    // echo $dstName . "<br>";

    $now= date('Y-m-d H:i');

    //메세지중에서 특수문자 사용가능성 있음.
    $message= addslashes($message);
    $title=addslashes($title);

    //MySQL DB에 데이터들 저장 [테이블명: market]
    $db =mysqli_connect("localhost","l**8","q1*****r4!","le****8");
    mysqli_query($db,"set names utf8");
//$dstName 파일 저장한 마지막 위치
    //데이터들[$name,$title,$message,$price,$dstName,$now] insert
    $sql="INSERT INTO market(name, title, msg, price, file, date) VALUES('$name','$title','$message','$price','$dstName','$now')";
    $result= mysqli_query($db,$sql);

    if($result) echo "게시글이 업로드 되었습니다.";
    else echo "게시글 업로드에 실패했습니다. 다시 시도해 주세요.";

    mysqli_close($db);

?>

loadDB.php

<?php
    header('Content-Type:application/json; charset=utf-8');

    $db= mysqli_connect("localhost","l****8","q*****r4!","le***8");
    mysqli_query($db,"set names utf8");

    $sql="SELECT * FROM market";
    $resultSet= mysqli_query($db,$sql);

    //결과표로부터 총 레코드(한줄:row) 수
    $rowNum= mysqli_num_rows($resultSet);

    //여러줄을 읽어야 하기에..ASSOC 연관배열
    //빈배열에 한줄짜리
    $rows= array();
    for($i=0; $i<$rowNum; $i++){
        $row= mysqli_fetch_array($resultSet, MYSQLI_ASSOC);
        $rows[$i]= $row;
       
    }

    //2차원 배열 -->json array변환 후 에코
    echo json_encode($rows);

    mysqli_close($db);

?>

main.xml

플로팅버튼= 벡터에셋

<?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">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/tool"
        android:layout_width="match_parent"
        app:title="게시판"
        android:background="#D715F8"
        app:titleTextColor="@color/white"
        app:titleCentered="true"
        android:layout_height="wrap_content"/>


    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_below="@id/tool"
        android:layout_height="wrap_content">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:orientation="vertical"/>

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>



    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/Widget.MaterialComponents.FloatingActionButton"
        app:backgroundTint="#D715F8"
        android:src="@drawable/baseline_border_color_11"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="16dp"/>



</RelativeLayout>

edit.xml

arrowicon= image에셋

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".EditActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#D715F8"
        app:title="글작성"
        app:titleTextColor="@color/white"
        app:titleCentered="true"
        app:navigationIcon="@drawable/ic_action_arrow_back"
        app:navigationIconTint="@color/white"/>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_name"
        android:layout_width="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:layout_height="wrap_content"
        android:hint="이름">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="text"/>
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_title"
        android:layout_width="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="8dp"
        android:layout_height="wrap_content"
        android:hint="제목">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="text"/>
    </com.google.android.material.textfield.TextInputLayout>
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_msg"
        android:layout_width="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="8dp"
        android:layout_height="wrap_content"
        android:hint="상세 내용">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textMultiLine"
            android:lines="5"
            android:gravity="top"/>
    </com.google.android.material.textfield.TextInputLayout>
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_price"
        android:layout_width="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="8dp"
        android:layout_height="wrap_content"
        android:hint="가격">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"/>
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/btn_select"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="select image"
        style="@style/Widget.MaterialComponents.Button"
        android:layout_gravity="right"
        android:layout_marginRight="16dp"
        android:textStyle="bold"
        android:backgroundTint="#D715F8"
        android:layout_marginTop="8dp"/>
    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_marginRight="16dp"
        android:layout_marginLeft="16dp"/>
    <Button
        android:id="@+id/btn_complete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/Widget.MaterialComponents.Button"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="8dp"
        android:backgroundTint="#D715F8"
        android:textStyle="bold"
        android:text="작성완료"/>


</LinearLayout>

item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="120dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:padding="8dp"
    android:layout_marginLeft="4dp"
    android:layout_marginRight="4dp">

    <androidx.cardview.widget.CardView
        android:id="@+id/cv"
        android:layout_width="120dp"
        android:layout_height="match_parent"
        app:cardCornerRadius="8dp">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@drawable/paris"/>

    </androidx.cardview.widget.CardView>
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="안드로이드 앱 개발"
        android:textSize="16sp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_toRightOf="@id/cv"
        android:layout_marginLeft="12dp"
        android:maxLines="2"
        android:ellipsize="end"/>
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="안드로이드 앱을 만들어 드립니다."
        android:textSize="12sp"
        android:textColor="#6C6B6B"
        android:layout_alignLeft="@id/tv_title"
        android:layout_below="@id/tv_title"
        android:maxLines="3"
        android:ellipsize="end"/>
    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="500000원"
        android:textSize="14sp"
        android:textColor="#D715F8"
        android:textStyle="bold"
        android:layout_alignLeft="@id/tv_msg"
        android:layout_alignParentBottom="true"/>
    <androidx.appcompat.widget.AppCompatImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@id/tv_msg"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:backgroundTint="@color/white"
        android:src="@drawable/baseline_star_24"/>



</RelativeLayout>

main.java

package com.eunji0118.ex69retrofitmarketapp;

import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

import com.eunji0118.ex69retrofitmarketapp.databinding.ActivityMainBinding;

import java.util.ArrayList;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    ArrayList<MarketItem> marketItems=new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding=ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.fab.setOnClickListener(view -> {
            Intent intent=new Intent(this, EditActivity.class);
            startActivity(intent);
        });
//새로고침만들기
        binding.refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                loadData();
                binding.refreshLayout.setRefreshing(false);  //로딩 아이콘 제거
            }
        });

    }

    @Override
    protected void onResume() {
        super.onResume();

        loadData();  //서버에서 데이터 가져오기..
    }

    void loadData(){

        Retrofit retrofit= RetrofitHelper.getRetrofitInstance();
        RetrofitService retrofitService=retrofit.create(RetrofitService.class);
        Call<ArrayList<MarketItem>> call =retrofitService.loadDataFromServer();
        call.enqueue(new Callback<ArrayList<MarketItem>>() {
            @Override
            public void onResponse(Call<ArrayList<MarketItem>> call, Response<ArrayList<MarketItem>> response) {
                marketItems.clear(); //기존의 것 없애고 다시

                ArrayList<MarketItem> items=response.body();
                //특정아이템 가져오기
                for (MarketItem item: items){
                    marketItems.add(0,item);//0번이 최신으로 들어가게
                }

                MarketAdapter adapter=new MarketAdapter(MainActivity.this,marketItems);
                binding.recyclerView.setAdapter(adapter);

            }

            @Override
            public void onFailure(Call<ArrayList<MarketItem>> call, Throwable t) {
                Toast.makeText(MainActivity.this, "error"+t.getMessage(), Toast.LENGTH_SHORT).show();

            }
        });
    }
}

edit.java

package com.eunji0118.ex69retrofitmarketapp;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.content.CursorLoader;

import android.app.AlertDialog;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.eunji0118.ex69retrofitmarketapp.databinding.ActivityEditBinding;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;

public class EditActivity extends AppCompatActivity {
    ActivityEditBinding binding;
    //3.여러개면 리스트로 만듦.
    String imgPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding= ActivityEditBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.toolbar.setNavigationOnClickListener(view -> finish());

        binding.btnSelect.setOnClickListener(view -> clickSelect());
        binding.btnComplete.setOnClickListener(view -> clickComplete());

    }

    void clickSelect(){
        Intent intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
        //2.
        resultLauncher.launch(intent);

    }
    //1.
    ActivityResultLauncher<Intent> resultLauncher=registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {
        if (result.getResultCode()==RESULT_CANCELED) return;
        Intent intent=result.getData();
        Uri uri=intent.getData();

        Glide.with(this).load(uri).into(binding.iv);

        imgPath=getRealPathFromUri(uri);
        //나오는거 확인
        // new AlertDialog.Builder(this).setMessage(imgPath).create().show();
    });
    //복붙
    //Uri -- > 절대경로로 바꿔서 리턴시켜주는 메소드
    String getRealPathFromUri(Uri uri){
        String[] proj= {MediaStore.Images.Media.DATA};
        CursorLoader loader= new CursorLoader(this, uri, proj, null, null, null);
        Cursor cursor= loader.loadInBackground();
        int column_index= cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String result= cursor.getString(column_index);
        cursor.close();
        return  result;
    }

    void clickComplete(){
        //작성한 데이터들 서버에 전송하기 (업로드)
        //전송할 데이터들[name, title,message, price, imgPath]
        String name=binding.inputLayoutName.getEditText().getText().toString();
        String title=binding.inputLayoutTitle.getEditText().getText().toString();
        String message=binding.inputLayoutMsg.getEditText().getText().toString();
        String price=binding.inputLayoutPrice.getEditText().getText().toString();

        //레트로핏 작업 5단계  헬퍼 만들고 ..
        Retrofit retrofit=RetrofitHelper.getRetrofitInstance();
        //명세서작성2,3
        RetrofitService retrofitService= retrofit.create(RetrofitService.class);
        //4.
        //string 데이터들
        Map<String,String> dataPart=new HashMap<>();
        dataPart.put("name",name);
        dataPart.put("title",title);
        dataPart.put("msg",message);
        dataPart.put("price",price);

        //이미지 파일(순서대로 포장)
        MultipartBody.Part filePart=null;
        if (imgPath!=null){
            File file=new File(imgPath);
            RequestBody body= RequestBody.create(MediaType.parse("image/*"),file);
            filePart=MultipartBody.Part.createFormData("img",file.getName(),body);
        }

        Call<String> call= retrofitService.postDataToServer(dataPart,filePart);
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                String s=response.body();
                Toast.makeText(EditActivity.this, "응답: "+s, Toast.LENGTH_SHORT).show();
                finish();
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {
                Toast.makeText(EditActivity.this, "error"+t.getMessage(), Toast.LENGTH_SHORT).show();



            }
        });


    }
}

marketAdapter.java

package com.eunji0118.ex69retrofitmarketapp;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.eunji0118.ex69retrofitmarketapp.databinding.RecyclerItemBinding;

import java.util.ArrayList;

public class MarketAdapter extends RecyclerView.Adapter<MarketAdapter.VH> {

    Context context;
    ArrayList<MarketItem> marketItems;

    public MarketAdapter(Context context, ArrayList<MarketItem> marketItems) {
        this.context = context;
        this.marketItems = marketItems;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView= LayoutInflater.from(context).inflate(R.layout.recycler_item,parent,false);
        return new VH(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        MarketItem item=marketItems.get(position);

        holder.binding.tvTitle.setText(item.title);
        holder.binding.tvMsg.setText(item.msg);
        holder.binding.tvPrice.setText(item.price+"원");
//서버주소는 절대적으로 바꿔야함.
        String url="http://lej0118.dothome.co.kr/05Retrofit/"+item.file;
        Glide.with(context).load(url).into(holder.binding.iv);

    }

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

    class VH extends RecyclerView.ViewHolder{
        RecyclerItemBinding binding;

        public VH(@NonNull View itemView) {
            super(itemView);

            binding=RecyclerItemBinding.bind(itemView);
        }
    }
}

marketItem.java

package com.eunji0118.ex69retrofitmarketapp;

public class MarketItem {
    int no;
    String name;
    String title;
    String msg;
    String price;
    String file;
    String date;

    public MarketItem(int no, String name, String title, String msg, String price, String file, String date) {
        this.no = no;
        this.name = name;
        this.title = title;
        this.msg = msg;
        this.price = price;
        this.file = file;
        this.date = date;
    }

    public MarketItem() {
    }
}

retrofithelper.java

package com.eunji0118.ex69retrofitmarketapp;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class RetrofitHelper {
    public static Retrofit getRetrofitInstance(){
        Retrofit.Builder builder=new Retrofit.Builder();
        builder.baseUrl("http://lej0118.dothome.co.kr");
        //스칼라먼저 쓸 것.
        builder.addConverterFactory(ScalarsConverterFactory.create());
        builder.addConverterFactory(GsonConverterFactory.create());

        Retrofit retrofit=builder.build();

        return retrofit;
    }
}

retrofitservice.interface

package com.eunji0118.ex69retrofitmarketapp;

import java.util.ArrayList;
import java.util.Map;

import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;

public interface RetrofitService {
    @Multipart//데이터 서버로 보내는 것.
    @POST("05Retrofit/insertDB.php") //식별자, 값  (다른박스 사용.파일, 글씨)
    Call<String>  postDataToServer(@PartMap Map<String,String> dataPart,
                                   @Part MultipartBody.Part filePart);

    @GET("05Retrofit/loadDB.php")
    Call<ArrayList<MarketItem>> loadDataFromServer();
}