Android

์‚ฌ์ง„์„ ํฌ์ŠคํŒ…ํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜(App) ๊ฐœ๋ฐœํ•˜๊ธฐ

567Rabbit 2024. 6. 17. 16:12


๋‹ค์Œ ๊ฒŒ์‹œ๋ฌผ์„ ์ฐธ๊ณ ํ•ด, ๋ฉ”์ธ์•กํ‹ฐ๋น„ํ‹ฐ์™€ ํšŒ์›๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ ํ™”๋ฉด๊ณผ Activity๋ฅผ ๋งŒ๋“ค๊ณ , config, string์„ ์ž‘์„ฑํ•ด์ค€๋‹ค.

 

https://codebunny99.tistory.com/170

 

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ ์•ฑ(app)์„ Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋งŒ๋“ค๊ธฐ

Restful API(๋ฉ”๋ชจ API)๋ฅผ ๋งŒ๋“ค๊ณ , MySQL๊ณผ ์—ฐ๋™ํ•˜์—ฌ Postman์œผ๋กœ ๊ฐœ๋ฐœํ•œ ํ›„, ์ง„ํ–‰ํ•˜์˜€๋‹ค.     ์•กํ‹ฐ๋น„ํ‹ฐ ์„ธ ๊ฐœ ๋งŒ๋“ค๊ธฐ   ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •ํ•˜๊ธฐ   Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ํ•˜๊ธฐ build.gradle.kts(:app)์—์„œimpleme

codebunny99.tistory.com

 

 

 

config์˜ ๋„๋ฉ”์ธ ์ฃผ์†Œ๋Š”,

 

 

 

 

 

ํฌ์ŠคํŒ… API๋ฅผ ๋งŒ๋“  vscode๋ฅผ sls deploy ํ•ด์ค€ ํ›„, ๋‚˜์˜จ endpoint ์ฃผ์†Œ๋กœ ํ•˜์˜€๋‹ค

 

https://codebunny99.tistory.com/117

 

instargram ์‚ฌ์ง„ ํฌ์ŠคํŒ… API (1) ๊ธฐ๋ณธ ์„ธํŒ…(Setting) ํ•˜๊ธฐ

API ๋งŒ๋“ค๊ธฐ ์ „์—, ๊ธฐ๋ณธ ์…‹ํŒ… ํ•˜๊ธฐ  (1). serverless๋กœ aws-posting-server๋ฅผ ๋งŒ๋“ค๊ณ  vscode๋กœ ์—ด์–ด์ฃผ์—ˆ๋‹ค. https://codebunny99.tistory.com/105 RestFul API๋ฅผ Serverless Framework๋กœ ์—ฐ๊ฒฐํ•˜๊ธฐ*** ์•„๋‚˜์ฝ˜๋‹ค ํ”„๋กฌํ”„ํŠธ์—์„œ ๊ฐ€์ƒ

codebunny99.tistory.com

 

 

 

 

 

 

ํฌ์ŠคํŠธ๋งจ์—์„œ๋„ ๋„๋ฉ”์ธ ์ฃผ์†Œ๋กœ ์ž˜ ์‹คํ–‰๋˜๋Š”์ง€ ์ ๊ฒ€ํ•œ ํ›„ ์ง„ํ–‰ํ•˜์˜€๋‹ค.

 

 

 

 

 

API ๋งŒ๋“ค๊ธฐ

 

 

 

 

NetworkClient

package com.~.postingapp.api;

import android.content.Context;

import com.~.postingapp.config.Config;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class NetworkClient {
    public static Retrofit retrofit;

    public static Retrofit getRetrofitClient(Context context){
        if(retrofit == null){
            // ํ†ต์‹  ๋กœ๊ทธ ํ™•์ธํ• ๋•Œ ํ•„์š”ํ•œ ์ฝ”๋“œ
            HttpLoggingInterceptor loggingInterceptor =
                    new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ๊ด€๋ จ ์ฝ”๋“œ
            OkHttpClient httpClient = new OkHttpClient.Builder()
                    .connectTimeout(1, TimeUnit.MINUTES)
                    .readTimeout(1, TimeUnit.MINUTES)
                    .writeTimeout(1, TimeUnit.MINUTES)
                    .addInterceptor(loggingInterceptor)
                    .build();
            // ๋„คํŠธ์›Œํฌ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ณ  ๋ฐ›๋Š”
            // ๋ ˆํŠธ๋กœํ• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ด€๋ จ ์ฝ”๋“œ
            retrofit = new Retrofit.Builder()
                    .baseUrl(Config.DOMAIN)
                    .client(httpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

 

 

 

UserApi

package com.~.postingapp.api;

import com.~.postingapp.model.User;
import com.~.postingapp.model.UserRes;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Header;
import retrofit2.http.POST;

public interface UserApi {

    // ํšŒ์›๊ฐ€์ž… API
    // ํ•จ์ˆ˜๋ช…์„ ์ž‘์„ฑํ•ด์ฃผ๊ณ , ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ž‘์„ฑ, ๋ฐ›์„ ๋ฐ์ดํ„ฐ๋Š” ๋ฆฌํ„ดํƒ€์ž…์— ์ž‘์„ฑ.
    @POST("/user/register")
    Call<UserRes> register(@Body User user);

    // ๋กœ๊ทธ์ธ API
    @POST("/user/login")
    Call<UserRes> login(@Body User user);

    // ๋กœ๊ทธ์•„์›ƒ API
    @DELETE("/user/logout")
    Call<UserRes> logout(@Header("Authorization") String token);
}

 

 

 

 

 

model ํŒจํ‚ค์ง€ User, UserRes ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

 

 

 

fileprovider.xml ํŒŒ์ผ ์ƒ์„ฑ ํ›„ ์ž‘์„ฑ

 

 

 

- ์นด๋ฉ”๋ผ์™€ ์•จ๋ฒ” ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ฝ”๋“œ์ด๋‹ค.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <root-path
        name="root"
        path="." />

    <cache-path
        name="cache"
        path="." /> <!--Context.getCacheDir() ๋‚ด๋ถ€ ์ €์žฅ์†Œ-->
    <files-path
        name="files"
        path="." /> <!--Context.getFilesDir() ๋‚ด๋ถ€ ์ €์žฅ์†Œ-->

    <external-path
        name="external"
        path="."/>  <!--  Environment.getExternalStorageDirectory() ์™ธ๋ถ€ ์ €์žฅ์†Œ-->
    <external-cache-path
        name="external-cache"
        path="."/> <!--  Context.getExternalCacheDir() ์™ธ๋ถ€ ์ €์žฅ์†Œ-->
    <external-files-path
        name="images"
        path="Pictures" /> <!--  Context.getExternalFilesDir() ์™ธ๋ถ€ ์ €์žฅ์†Œ-->
</paths>

 

 

 

 

 

AndroidManifest.xml ์„ค์ •

 

- ํ™˜๊ฒฝ์„ค์ • ํ•˜๊ธฐ

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

<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

 

 

 

๊ฒ€์ •์ƒ‰์œผ๋กœ ์ง€์šด ๋ถ€๋ถ„์€, ์ž๋™์™„์„ฑ ๋  ๊ฒƒ์ด๋‹ค.

 

<provider
    android:authorities="com.~.cameraapp.fileprovider"
    android:name="androidx.core.content.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/fileprovider"/>
</provider>

 

 

 

 

 

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

 

build.gradle.kts(:app) ์—์„œ, ๋งจ ์•„๋žซ์ค„ ์ž‘์„ฑ.

 

    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    implementation("com.squareup.retrofit2:converter-gson:2.11.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

    implementation("com.github.bumptech.glide:glide:4.16.0")
    implementation("commons-io:commons-io:2.4")

 

 

 

activity_main.xml

 

ํ™”๋ฉด ์ž‘์„ฑ

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/btnAdd"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

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

        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_margin="10dp"
            android:text="ํฌ์ŠคํŒ… ์ƒ์„ฑ"
            android:textSize="24sp" />
    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

 

 

Activity ๋งŒ๋“ค์–ด์„œ, activity_add.xml, AddActivity.java ์ž‘์„ฑํ•˜๊ธฐ

 

ํ™”๋ฉด ์ž‘์„ฑ

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AddActivity">

    <ImageView
        android:id="@+id/imgPhoto"
        android:layout_width="227dp"
        android:layout_height="235dp"
        android:layout_marginStart="80dp"
        android:layout_marginTop="96dp"
        android:layout_marginEnd="80dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/add_photo" />

    <Button
        android:id="@+id/btnSave"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="10dp"
        android:text="ํฌ์ŠคํŠธ ์ƒ์„ฑ"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editContent" />

    <EditText
        android:id="@+id/editContent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="36dp"
        android:layout_marginEnd="10dp"
        android:ems="10"
        android:hint="๋‚ด์šฉ ์ž…๋ ฅ..."
        android:inputType="text"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imgPhoto" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

Addactivity.java

 

- ํ™”๋ฉด๊ณผ ์—ฐ๊ฒฐํ•˜๊ณ  ๋กœ์ง์„ ์ž‘์„ฑํ•˜๊ณ  ์นด๋ฉ”๋ผ, ์•จ๋ฒ”์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

 

package com.~.postingapp;

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.ColorDrawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.google.android.material.snackbar.Snackbar;
import com.~.postingapp.api.NetworkClient;
import com.~.postingapp.api.PostingApi;
import com.~.postingapp.config.Config;
import com.~.postingapp.model.Res;

import android.Manifest;

import org.apache.commons.io.IOUtils;

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


public class AddActivity extends AppCompatActivity {

    ImageView imgPhoto;
    EditText editContent;
    Button btnSave;

    File photoFile;

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

        imgPhoto = findViewById(R.id.imgPhoto);
        editContent = findViewById(R.id.editContent);
        btnSave = findViewById(R.id.btnSave);

        // ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด, ์นด๋ฉ”๋ผ๋กœ ์ฐ์„๊ฒƒ์ธ์ง€, ์•จ๋ฒ”์—์„œ ๊ณ ๋ฅผ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ
        // ์•Œ๋ŸฌํŠธ ๋‹ค์ด์–ผ๋กœ๊ทธ ๋„์šด๋‹ค.

        imgPhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showDialog();
            }
        });

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // ํŒŒ์ผ์ด ์žˆ์–ด์•ผ ํ•˜๊ณ  ๋‚ด์šฉ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค
                String content = editContent.getText().toString().trim();

                if(content.isEmpty() || photoFile == null){
                    Snackbar.make(btnSave, "์‚ฌ์ง„๊ณผ ๋‚ด์šฉ์€ ํ•„์ˆ˜ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค",Snackbar.LENGTH_SHORT).show();
                    return;
                }

                // ๋ ˆํŠธ๋กœํ• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์šฉํ•ด์„œ ๋„คํŠธ์›Œํฌ๋กœ ํ˜ธ์ถœํ•œ๋‹ค.
                showProgress();
                Retrofit retrofit = NetworkClient.getRetrofitClient(AddActivity.this);
                PostingApi api = retrofit.create(PostingApi.class);

                SharedPreferences sp = getSharedPreferences(Config.SP_NAME, MODE_PRIVATE);
                String token = sp.getString("token","");

                // ๋ณด๋‚ผ ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ
                RequestBody fileBody = RequestBody.create(photoFile, MediaType.parse("image/jpeg"));
                MultipartBody.Part image = MultipartBody.Part.createFormData("image", photoFile.getName(), fileBody);

                // ๋ณด๋‚ผ ํ…์ŠคํŠธ(content) ๋งŒ๋“ค๊ธฐ
                RequestBody textBody = RequestBody.create(content, MediaType.parse("text/plain"));

                Call<Res> call = api.addPosting("Bearer " + token, image, textBody);

                call.enqueue(new Callback<Res>() {
                    @Override
                    public void onResponse(Call<Res> call, Response<Res> response) {
                        dismissProgress();

                        if(response.isSuccessful()){

                            Snackbar.make(btnSave, "์ž˜ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", Snackbar.LENGTH_SHORT).show();
                            finish();

                        } else {

                        }

                    }

                    @Override
                    public void onFailure(Call<Res> call, Throwable throwable) {
                        dismissProgress();
                    }
                });
            }
        });

    }

    private void showDialog(){
        AlertDialog.Builder builder = new AlertDialog.Builder(AddActivity.this);
        builder.setTitle(R.string.alert_title);
        builder.setItems(R.array.alert_photo, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                if(i == 0){
                    // ์ฒซ๋ฒˆ์งธ ํ•ญ๋ชฉ ๋ˆŒ๋ €์„๋•Œ
                    // ์นด๋ฉ”๋ผ๋กœ ์‚ฌ์ง„์ฐ๊ธฐ
                    camera();

                }else if(i == 1){
                    // ๋‘๋ฒˆ์งธ ํ•ญ๋ชฉ ๋ˆŒ๋ €์„๋•Œ
                    album();
                }
            }
        });
        builder.show();
    }


    private void camera(){
        int permissionCheck = ContextCompat.checkSelfPermission(
                AddActivity.this, Manifest.permission.CAMERA);

        if(permissionCheck != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(AddActivity.this,
                    new String[]{android.Manifest.permission.CAMERA} ,
                    1000);
            Toast.makeText(AddActivity.this, "์นด๋ฉ”๋ผ ๊ถŒํ•œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
                    Toast.LENGTH_SHORT).show();
            return;
        } else {
            Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if(i.resolveActivity(AddActivity.this.getPackageManager())  != null  ){

                // ์‚ฌ์ง„์˜ ํŒŒ์ผ๋ช…์„ ๋งŒ๋“ค๊ธฐ
                String fileName = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
                photoFile = getPhotoFile(fileName);

                Uri fileProvider = FileProvider.getUriForFile(AddActivity.this,Config.FILE_PROVIDER,photoFile);
                i.putExtra(MediaStore.EXTRA_OUTPUT, fileProvider);
                startActivityForResult(i, 100);

            } else{
                Toast.makeText(AddActivity.this, "์ดํฐ์—๋Š” ์นด๋ฉ”๋ผ ์•ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.",
                        Toast.LENGTH_SHORT).show();
            }
        }


    }

    private void album(){
        if(checkPermission()){
            displayFileChoose();
        }else{
            requestPermission();
        }
    }

    private boolean checkPermission(){
        int result = ContextCompat.checkSelfPermission(AddActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if(result == PackageManager.PERMISSION_DENIED){
            return false;
        }else{
            return true;
        }
    }

    private void requestPermission() {
        if(ActivityCompat.shouldShowRequestPermissionRationale(AddActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)){
            Log.i("DEBUGGING5", "true");
            Toast.makeText(AddActivity.this, "๊ถŒํ•œ ์ˆ˜๋ฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
                    Toast.LENGTH_SHORT).show();
        }else{
            Log.i("DEBUGGING6", "false");
            ActivityCompat.requestPermissions(AddActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 500);
        }
    }

    private void displayFileChoose() {
        Intent i = new Intent();
        i.setType("image/*");
        i.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(i, "SELECT IMAGE"), 300);
    }

    private File getPhotoFile(String fileName) {
        File storageDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        try{
            return File.createTempFile(fileName, ".jpg", storageDirectory);
        }catch (IOException e){
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1000: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(AddActivity.this, "๊ถŒํ•œ ํ—ˆ๊ฐ€ ๋˜์—ˆ์Œ",
                            Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(AddActivity.this, "์•„์ง ์Šน์ธํ•˜์ง€ ์•Š์•˜์Œ",
                            Toast.LENGTH_SHORT).show();
                }
                break;
            }
            case 500: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(AddActivity.this, "๊ถŒํ•œ ํ—ˆ๊ฐ€ ๋˜์—ˆ์Œ",
                            Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(AddActivity.this, "์•„์ง ์Šน์ธํ•˜์ง€ ์•Š์•˜์Œ",
                            Toast.LENGTH_SHORT).show();
                }

            }

        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == 100 && resultCode == RESULT_OK){

            Bitmap photo = BitmapFactory.decodeFile(photoFile.getAbsolutePath());

            ExifInterface exif = null;
            try {
                exif = new ExifInterface(photoFile.getAbsolutePath());
            } catch (IOException e) {
                e.printStackTrace();
            }
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_UNDEFINED);
            photo = rotateBitmap(photo, orientation);

            // ์••์ถ•์‹œํ‚จ๋‹ค. ํ•ด์ƒ๋„ ๋‚ฎ์ถฐ์„œ
            OutputStream os;
            try {
                os = new FileOutputStream(photoFile);
                photo.compress(Bitmap.CompressFormat.JPEG, 20, os);
                os.flush();
                os.close();
            } catch (Exception e) {
                Log.e(getClass().getSimpleName(), "Error writing bitmap", e);
            }

            photo = BitmapFactory.decodeFile(photoFile.getAbsolutePath());

            imgPhoto.setImageBitmap(photo);
            imgPhoto.setScaleType(ImageView.ScaleType.CENTER_CROP);

            // ๋„คํŠธ์›Œํฌ๋กœ ๋ฐ์ดํ„ฐ ๋ณด๋‚ธ๋‹ค.



        }else if(requestCode == 300 && resultCode == RESULT_OK && data != null &&
                data.getData() != null){

            Uri albumUri = data.getData( );
            String fileName = getFileName( albumUri );
            try {

                ParcelFileDescriptor parcelFileDescriptor = getContentResolver( ).openFileDescriptor( albumUri, "r" );
                if ( parcelFileDescriptor == null ) return;
                FileInputStream inputStream = new FileInputStream( parcelFileDescriptor.getFileDescriptor( ) );
                photoFile = new File( this.getCacheDir( ), fileName );
                FileOutputStream outputStream = new FileOutputStream( photoFile );
                IOUtils.copy( inputStream, outputStream );

//                //์ž„์‹œํŒŒ์ผ ์ƒ์„ฑ
//                File file = createImgCacheFile( );
//                String cacheFilePath = file.getAbsolutePath( );


                // ์••์ถ•์‹œํ‚จ๋‹ค. ํ•ด์ƒ๋„ ๋‚ฎ์ถฐ์„œ
                Bitmap photo = BitmapFactory.decodeFile(photoFile.getAbsolutePath());
                OutputStream os;
                try {
                    os = new FileOutputStream(photoFile);
                    photo.compress(Bitmap.CompressFormat.JPEG, 20, os);
                    os.flush();
                    os.close();
                } catch (Exception e) {
                    Log.e(getClass().getSimpleName(), "Error writing bitmap", e);
                }

                imgPhoto.setImageBitmap(photo);
                imgPhoto.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

//                imageView.setImageBitmap( getBitmapAlbum( imageView, albumUri ) );

            } catch ( Exception e ) {
                e.printStackTrace( );
            }

            // ๋„คํŠธ์›Œํฌ๋กœ ๋ณด๋‚ธ๋‹ค.
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    //์•จ๋ฒ”์—์„œ ์„ ํƒํ•œ ์‚ฌ์ง„์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ
    public String getFileName( Uri uri ) {
        Cursor cursor = getContentResolver( ).query( uri, null, null, null, null );
        try {
            if ( cursor == null ) return null;
            cursor.moveToFirst( );
            @SuppressLint("Range") String fileName = cursor.getString( cursor.getColumnIndex( OpenableColumns.DISPLAY_NAME ) );
            cursor.close( );
            return fileName;

        } catch ( Exception e ) {
            e.printStackTrace( );
            cursor.close( );
            return null;
        }
    }


    public static Bitmap rotateBitmap(Bitmap bitmap, int orientation) {

        Matrix matrix = new Matrix();
        switch (orientation) {
            case ExifInterface.ORIENTATION_NORMAL:
                return bitmap;
            case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                matrix.setScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                matrix.setRotate(180);
                break;
            case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                matrix.setRotate(180);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_TRANSPOSE:
                matrix.setRotate(90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_90:
                matrix.setRotate(90);
                break;
            case ExifInterface.ORIENTATION_TRANSVERSE:
                matrix.setRotate(-90);
                matrix.postScale(-1, 1);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                matrix.setRotate(-90);
                break;
            default:
                return bitmap;
        }
        try {
            Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            bitmap.recycle();
            return bmRotated;
        }
        catch (OutOfMemoryError e) {
            e.printStackTrace();
            return null;
        }
    }

    // ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜, ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜, ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•œ๋‹ค!
    Dialog dialog;
    void showProgress(){
        dialog = new Dialog(this);
        dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        dialog.setContentView(new ProgressBar(this));
        dialog.setCancelable(false);
        dialog.setCanceledOnTouchOutside(false);
        dialog.show();
    }
    void dismissProgress(){
        dialog.dismiss();
    }
    
}

 

 

 

 

PostingApi ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

 

package com.~.postingapp.api;

import com.~.postingapp.model.Res;

import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Part;

public interface PostingApi {

    @POST("/posting")
    Call<Res> addPosting(@Header("Authorization") String token,
                         @Part MultipartBody.Part image,  //ํŒŒํŠธ๋กœ ๋‚˜๋ˆ ์„œ ๋ณด๋‚ด๋ผ ์šฉ๋Ÿ‰ ํฌ๋ฏ€๋กœ
                         @Part("content")RequestBody content);
}

 

 

@Part MultipartBody 

- Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ€ํ‹ฐํŒŒํŠธ ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ์• ๋…ธํ…Œ์ด์…˜์ด๋‹ค.

- ์ด๋ฅผ ํ†ตํ•ด ํŒŒ์ผ ๋ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

 

Res ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

 

 

 

 

 

activity_row ๋งŒ๋“ค๊ธฐ

 

 

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="5dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="5dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/imgPhoto"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="4"
                app:srcCompat="@drawable/add_photo" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="5"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/txtContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="TextView"
                    android:textColor="#000000"
                    android:textSize="23sp" />

                <TextView
                    android:id="@+id/txtEmail"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="TextView"
                    android:textColor="#000000"
                    android:textSize="23sp" />

                <TextView
                    android:id="@+id/txtCreatedAt"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="TextView"
                    android:textColor="#000000"
                    android:textSize="23sp" />

                <ImageView
                    android:id="@+id/imgLike"
                    android:src="@drawable/like_24dp"
                    android:layout_width="35dp"
                    android:layout_height="35dp" />

            </LinearLayout>

        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

 

 

 

 

 

MainActivity. java ์ž‘์„ฑ

 

package com.~.postingapp;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.~.postingapp.adapter.PostingAdapter;
import com.~.postingapp.api.NetworkClient;
import com.~.postingapp.api.PostingApi;
import com.~.postingapp.config.Config;
import com.~.postingapp.model.Posting;
import com.~.postingapp.model.PostingList;

import java.util.ArrayList;

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

public class MainActivity extends AppCompatActivity {

    ProgressBar progressBar;
    Button btnAdd;
    RecyclerView recyclerView;
    ArrayList<Posting> postingArrayList = new ArrayList<>();
    PostingAdapter adapter;

    String token;

    // ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ์— ํ•„์š”ํ•œ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋“ค!!
    int offset = 0;
    int limit = 5;
    int count;

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

        SharedPreferences sp = getSharedPreferences(Config.SP_NAME, MODE_PRIVATE);
        token = sp.getString("token", "");
        if(token.isEmpty()){
            Intent intent = new Intent(MainActivity.this, LoginActivity.class);
            startActivity(intent);
            finish();
            return;
        }

        progressBar = findViewById(R.id.progressBar);
        btnAdd = findViewById(R.id.btnAdd);
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                int lastPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();
                int totalCount = recyclerView.getAdapter().getItemCount();

                if(lastPosition + 1 == totalCount){

                    if(count == limit){
                        // ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€๋กœ ์š”์ฒญํ•œ๋‹ค.
                        addNetworkData();
                    }
                }

            }
        });

        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, AddActivity.class);
                startActivity(intent);
            }
        });


    }

    private void addNetworkData() {

        progressBar.setVisibility(View.VISIBLE);

        Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);

        PostingApi api = retrofit.create(PostingApi.class);

        Call<PostingList> call = api.getPostingList("Bearer " + token, offset, limit );

        call.enqueue(new Callback<PostingList>() {
            @Override
            public void onResponse(Call<PostingList> call, Response<PostingList> response) {
                progressBar.setVisibility(View.GONE);

                if(response.isSuccessful()){
                    PostingList postingList = response.body();

                    count = postingList.count;

                    offset = offset + count;

                    postingArrayList.addAll( postingList.items );

                    adapter.notifyDataSetChanged();

                }else{

                }
            }

            @Override
            public void onFailure(Call<PostingList> call, Throwable throwable) {
                progressBar.setVisibility(View.GONE);
            }
        });

    }

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

        // ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค.
        getNetworkData();

    }

    private void getNetworkData() {

        progressBar.setVisibility(View.VISIBLE);

        postingArrayList.clear();

        offset = 0;

        Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);

        PostingApi api = retrofit.create(PostingApi.class);

        Call<PostingList> call = api.getPostingList("Bearer "+token, offset, limit);

        call.enqueue(new Callback<PostingList>() {
            @Override
            public void onResponse(Call<PostingList> call, Response<PostingList> response) {
                progressBar.setVisibility(View.GONE);

                if(response.isSuccessful()){
                    PostingList postingList = response.body();

                    count = postingList.count;

                    offset = offset + count;

                    postingArrayList.addAll( postingList.items );

                    adapter = new PostingAdapter(MainActivity.this, postingArrayList);

                    recyclerView.setAdapter(adapter);

                }else{

                }

            }

            @Override
            public void onFailure(Call<PostingList> call, Throwable throwable) {
                progressBar.setVisibility(View.GONE);
            }
        });

    }
}

 

 

 

 

Posting ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

 

package com.~.postingapp.model;

public class Posting {
    public int id;
    public int userId;
    public String imageUrl;
    public String content;
    public String createdAt;
    public String updatedAt;
    public String email;
    public int isLike;
}

 

 

 

 

 

PostingList ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

package com.~.postingapp.model;

import java.util.ArrayList;

public class PostingList {

    public String result;
    public ArrayList<Posting> items;
    public int count;

}

 

 

 

 

 

 

PostingAdapter ๋งŒ๋“ค๊ธฐ

 

- activity_row์™€ MainActivity์˜ ํ™”๋ฉด์„ ์—ฐ๊ฒฐํ•ด์ค€๋‹ค.

package com.~.postingapp.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

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

import com.bumptech.glide.Glide;
import com.~.postingapp.R;
import com.~.postingapp.model.Posting;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;

public class PostingAdapter extends RecyclerView.Adapter<PostingAdapter.ViewHolder> {

    Context context;
    ArrayList<Posting> postingArrayList;

    SimpleDateFormat sf;
    SimpleDateFormat df;

    public PostingAdapter(Context context, ArrayList<Posting> postingArrayList) {
        this.context = context;
        this.postingArrayList = postingArrayList;

        //2024-05-30T07:23:52
        sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sf.setTimeZone(TimeZone.getTimeZone("UTC"));
        df.setTimeZone(TimeZone.getDefault());

    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.activity_row, parent, false);
        return new PostingAdapter.ViewHolder(view);
    }

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

        Posting posting = postingArrayList.get(position);

        Glide.with(context).load( posting.imageUrl ).into( holder.imgPhoto );

        holder.txtContent.setText( posting.content );
        holder.txtEmail.setText( posting.email );
        holder.txtCreatedAt.setText( posting.createdAt );

        if( posting.isLike == 0 ){
            holder.imgLike.setImageResource(R.drawable.like_24dp);
        }else{
            holder.imgLike.setImageResource(R.drawable.like2);
        }

        try {
            Date date = sf.parse( posting.createdAt );
            String localTime = df.format(date);
            holder.txtCreatedAt.setText(localTime);
        } catch (ParseException e) {
            // ๋กœ๊ทธ๋ฅผ ๋‚จ๊ฒจ์„œ ๋””๋ฒ„๊น…ํ•œ๋‹ค.
        }

    }

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

    public class ViewHolder extends RecyclerView.ViewHolder{
        ImageView imgPhoto;
        TextView txtContent;
        TextView txtEmail;
        TextView txtCreatedAt;
        ImageView imgLike;

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

            imgPhoto = itemView.findViewById(R.id.imgPhoto);
            txtContent = itemView.findViewById(R.id.txtContent);
            txtEmail = itemView.findViewById(R.id.txtEmail);
            txtCreatedAt = itemView.findViewById(R.id.txtCreatedAt);
            imgLike = itemView.findViewById(R.id.imgLike);
        }
    }
}

 

 

 

 

 

PostingApi ๋งŒ๋“ค๊ธฐ

 

package com.~.postingapp.api;

import com.~.postingapp.model.PostingList;
import com.~.postingapp.model.Res;

import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.Query;

public interface PostingApi {

    // ํฌ์ŠคํŒ… ์ƒ์„ฑ API
    @POST("/posting")
    Call<Res> addPosting(@Header("Authorization") String token,
                         @Part MultipartBody.Part image,
                         @Part("content") RequestBody content);

    // ์นœ๊ตฌ๋“ค์˜ ํฌ์ŠคํŒ… ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๋Š” API
    @GET("/posting")
    Call<PostingList> getPostingList(@Header("Authorization") String token,
                                     @Query("offset") int offset,
                                     @Query("limit") int limit);

}