서비스 파일 생성

 

 

 

 

 

 

AndroidManifest.xml

위 처럼 ide를 통해 만들면 안드로이드 메니페스트에 자동으로 등록된다

 

 

3개를 추가적으로 생성

 

 

command는 서비스 쪽으로 전달한 인텐트 객체의 데이터가 어떤 목적으로 사용되는지 구별하기 위해 넣은것

 

 

startService()메서드에 담은 인텐트 객체는 MyService 클래스의 onStartCommand()메서드로 전달됨

 

 

확인해보면 데이터가 출력됨

 

 

 

MyService

화면이 없는 service에서 화면이 있는 액티비티를 띄우려면 새로운 태스크를 만들어야한다

MainActivity객체가 이미 메모리에 만들어져 있을 때 재사용하도록 

intent.FLAG_ACTIVITY_SINGLE_TOP | intent.FLAG_ACTIVITY_CLEAR_TOP 를 플래그에 추가

 

 

MainActivity

 

 

5초뒤 서비스에서 전달된 인텐트를 메인 액티비티에서 받아서 처리

 

----------

 

Room을 이용한 연락처 앱 만들기

 

새프로젝트 생성

 

복붙

https://github.com/codingspecialist/Android-ContactApp-Room-v1

 

codingspecialist/Android-ContactApp-Room-v1

Contribute to codingspecialist/Android-ContactApp-Room-v1 development by creating an account on GitHub.

github.com

 

 

액션바 바꾸기

onCreateOptionsMenu 액션바에 메뉴 넣기

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---

메인액티비티 수정

https://ondolroom.tistory.com/664

 

안드로이드 // MainActivity 구성방식

package com.jaybon.contactsapp; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.re..

ondolroom.tistory.com

---

 

편지버튼 누르면 창 뜨고 인서트

 

 

 

-----------

 

전체삭제

 

 

 

 

 

 

---------

프로그래밍의 목적은 데이터베이스의 내용과 동기화 하는 것을 따라감

// DB내용변경 -> 어댑터 데이터 변경 -> UI 갱신
// MVVM의 경우 DB내용변경 -> 어댑터 데이터 변경시 UI 자동갱신
// 리액티브 프로그래밍의 경우 DB내용변경시 어댑터 데이터 자동갱신 +UI 자동갱신

--------

 

클릭시 상세보기

연락처 개별삭제

 

 

 

----------

연락처 수정하기

 

생성자 오버로딩 추가

@Ignore 를 추가하여 Room 데이터베이스에 적용되지 않도록 함

(id는 오토인크리먼트를 사용할 것이므로)

 

리스너를 달아줌

 

 

----------------

 

https://github.com/codingspecialist/Android-ContactApp-Room-ImageUpload-v2

 

codingspecialist/Android-ContactApp-Room-ImageUpload-v2

Contribute to codingspecialist/Android-ContactApp-Room-ImageUpload-v2 development by creating an account on GitHub.

github.com

 

고투 앨범 (프로필사진 앨범 또는 카메라 접근 )

 

매니페스트에 아래코드 추가

 

MainActivity 아래 복붙

더보기
    // 앨범으로 이동
    private void goToAlbum() {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        startActivityForResult(intent, PICK_FROM_ALBUM);
    }

    // 이미지 채우기
    private void setImage() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap originalBm = BitmapFactory.decodeFile(tempFile.getAbsolutePath(), options);
        ivProfile.setImageBitmap(originalBm);
    }

    // URI로 이미지 실제 경로 가져오기
    private String getRealPathFromURI(Uri contentURI) {
        String result;
        Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
        if (cursor == null) { // Source is Dropbox or other similar local file path
            result = contentURI.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }

    // 이미지 선택 후 이미지 채우기
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_FROM_ALBUM) {
            Uri photoUri = data.getData();
            imageRealPath = getRealPathFromURI(photoUri);
            tempFile = new File(imageRealPath);
            setImage();
        }
    }

 

 

 

전역변수에 추가

더보기
    // 사진 업로드
    private CircleImageView ivProfile;
    private File tempFile;
    private String imageRealPath;
    private static final int PICK_FROM_ALBUM = 1;
    private static final int PICK_FROM_CAMERA = 2;

 

Intent는 내장앱이나 다른앱으로도 접근 할 수 있다 (다른앱에서 허용되어있을 경우)

ACTION_PICK

 

결과를 리턴받겠다는 뜻

 

 

사진첩으로 이동한다

 

 

이미지 선택하면 이쪽으로 온다

 

 

 

setImage안되면 변경

 

https://lktprogrammer.tistory.com/188

 

[Android] 안드로이드 - 갤러리에서 이미지 가져오기

1. /res/layout/activity_main.xml ▼ 갤러리에서 가져온 사진을 표현하기 위해 ImageView 위젯 하나를 배치하였습니다. 해당 예제에서는 화면에 배치되어 있는 ImageView..

lktprogrammer.tistory.com

 

-----------

파일 구조변경

ImageUpload

더보기
package com.jaybon.contactsapp.util;

import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;

import com.jaybon.contactsapp.MainActivity;

import de.hdodenhof.circleimageview.CircleImageView;

public class ImageUpload {

    public static final int PICK_FROM_ALBUM = 1;

    // 앨범으로 이동
    public static void goToAlbum(MainActivity mainActivity) {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        mainActivity.startActivityForResult(intent, ImageUpload.PICK_FROM_ALBUM);
    }

    // 이미지 채우기
    public static void setImage(String profileURL, CircleImageView ivProfile) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap originalBm = BitmapFactory.decodeFile(profileURL, options);
        ivProfile.setImageBitmap(originalBm);
    }

    // URI로 이미지 실제 경로 가져오기
    public static String getRealPathFromURI(Uri contentURI, MainActivity mainActivity) {
        String result;
        Cursor cursor = mainActivity.getContentResolver().query(contentURI, null, null, null, null);
        if (cursor == null) {
            result = contentURI.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }
}

 

MainActivity

더보기
package com.jaybon.contactsapp;

import android.Manifest;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Room;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;
import com.jaybon.contactsapp.adapter.ContactAdapter;
import com.jaybon.contactsapp.db.ContactAppDatabase;
import com.jaybon.contactsapp.db.model.Contact;
import com.jaybon.contactsapp.service.ContactService;
import com.jaybon.contactsapp.util.ImageUpload;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import de.hdodenhof.circleimageview.CircleImageView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Main_Activity";
    private Context mContext = MainActivity.this;

    private Toolbar toolbar;
    private RecyclerView recyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private ContactAdapter adapter;
    private FloatingActionButton fab;

    private ContactAppDatabase contactAppDatabase;
    private ContactService contactService;
    private List<Contact> contacts;

    // 사진 업로드
    private CircleImageView ivProfile;
    private File tempFile;
    private String imageRealPath;
    private static final int PICK_FROM_ALBUM = 1;
    private static final int PICK_FROM_CAMERA = 2;

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

        // DB관련
        contactAppDatabase = Room.databaseBuilder(getApplicationContext(), ContactAppDatabase.class, "ContactDB")
                .allowMainThreadQueries()
                .fallbackToDestructiveMigration()
                .build();
        contactService = new ContactService(contactAppDatabase.contactRepository());

        initData();
        initObject();
        initDesign();
        initListener();

        tedPermission();
    }

    private void initData(){
        contacts = contactService.연락처전체보기();
    }

    private void initObject(){
        toolbar = findViewById(R.id.toolbar);
        recyclerView = findViewById(R.id.recycler_view_contacts);
        fab = findViewById(R.id.fab);
        ivProfile = findViewById(R.id.iv_profile);
        adapter = new ContactAdapter((MainActivity)mContext , contacts);
    }

    private void initDesign(){
        setSupportActionBar(toolbar);
        getSupportActionBar().setTitle(" Contact App");

        mLayoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setAdapter(adapter);
    }

    private void initListener(){
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addContactDialog();
            }
        });
    }

    public void addContactDialog(){
        View dialogView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.layout_add_contact, null);
        final EditText etName = dialogView.findViewById(R.id.name);
        final EditText etEmail = dialogView.findViewById(R.id.email);

        // 갤러리 사진 가져오기
        ivProfile = dialogView.findViewById(R.id.iv_profile);
        ivProfile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ImageUpload.goToAlbum((MainActivity) mContext);
            }
        });

        AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this);
        dlg.setTitle("연락처 등록");
        dlg.setView(dialogView);
        dlg.setPositiveButton("등록", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                createContact(etName.getText().toString(), etEmail.getText().toString());
            }
        });
        dlg.setNegativeButton("닫기", null);
        dlg.show();
    }

    public void editContactDialog(final Contact contact){
        View dialogView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.layout_add_contact, null);
        final EditText etName = dialogView.findViewById(R.id.name);
        final EditText etEmail = dialogView.findViewById(R.id.email);

        // 갤러리 사진 수정하기
        ivProfile = dialogView.findViewById(R.id.iv_profile);
        ivProfile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //goToAlbum();
            }
        });

        etName.setText(contact.getName());
        etEmail.setText(contact.getEmail());

        AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this);
        dlg.setTitle("연락처 수정");
        dlg.setView(dialogView);
        dlg.setPositiveButton("수정", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
//                createContact(etName.getText().toString(), etEmail.getText().toString());
                Contact updateContact = new Contact(contact.getId(), etName.getText().toString(), etEmail.getText().toString());

                contactService.연락처수정(updateContact);
                notifyListener();

            }
        });
        dlg.setNegativeButton("삭제", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                contactService.연락처삭제(contact.getId());
                notifyListener();
            }
        });
        dlg.show();
    }

    private void createContact(String name, String email) {
        long contactId = contactService.연락처등록(new Contact(name, email));
        Contact contact = contactService.연락처상세보기(contactId);
        adapter.addItem(contact);
    }

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

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {

//        long menuId = item.getItemId(); // 개별 연락처의 번호

        contactService.연락처전체삭제();
        Log.d(TAG, "onOptionsItemSelected: 삭제됨");

        notifyListener();

        return true;
    }

    public void notifyListener(){
        // DB내용변경 -> 어댑터 데이터 변경 -> UI 갱신
        // MVVM의 경우 DB내용변경 -> 어댑터 데이터 변경시 UI 자동갱신
        // 리액티브 프로그래밍의 경우 DB내용변경시 어댑터 데이터 자동갱신 +UI 자동갱신
        adapter.addItems(contactService.연락처전체보기()); // adapter 내용변경
        adapter.notifyDataSetChanged(); // ui 갱신
    }

//    // 앨범으로 이동
//    private void goToAlbum() {
//        Intent intent = new Intent(Intent.ACTION_PICK);
//        intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
//        startActivityForResult(intent, PICK_FROM_ALBUM);
//    }
//
//    // 이미지 채우기
//    public void setImage() {
//        BitmapFactory.Options options = new BitmapFactory.Options();
//        Bitmap originalBm = BitmapFactory.decodeFile(tempFile.getAbsolutePath(), options);
//        ivProfile.setImageBitmap(originalBm);
//    }
//
//    // URI로 이미지 실제 경로 가져오기
//    private String getRealPathFromURI(Uri contentURI) {
//        String result;
//        Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
//        if (cursor == null) { // Source is Dropbox or other similar local file path
//            result = contentURI.getPath();
//        } else {
//            cursor.moveToFirst();
//            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
//            result = cursor.getString(idx);
//            cursor.close();
//        }
//        return result;
//    }

    // 이미지 선택 후 이미지 채우기
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d(TAG, "onActivityResult: 사진 선택 후 여기");
        
        if (requestCode == PICK_FROM_ALBUM) { // 앨범에서 가져온 데이터
            Uri photoUri = data.getData();

            imageRealPath = ImageUpload.getRealPathFromURI(photoUri, (MainActivity) mContext);

            Log.d(TAG, "onActivityResult: imageRealPath" + imageRealPath);

            tempFile = new File(imageRealPath);
            ImageUpload.setImage(tempFile.getAbsolutePath(), ivProfile);

        } else if(requestCode == PICK_FROM_CAMERA) {
            // 카메라로 찍은 데이터
        }
    }

    // ------------- 아래는 사진 업로드를 위한 추가 코드 ---------------------
    // https://dd00oo.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B0%A4%EB%9F%AC%EB%A6%AC%EC%9D%98-%EC%8B%A4%EC%A0%9C%EA%B2%BD%EB%A1%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0

    // 권한 관련
    private void tedPermission() {
        PermissionListener permissionListener = new PermissionListener() {
            @Override
            public void onPermissionGranted() {
                // 권한 요청 성공
            }

            @Override
            public void onPermissionDenied(ArrayList<String> deniedPermissions) {
                // 권한 요청 실패
            }
        };

        TedPermission.with(this)
                .setPermissionListener(permissionListener)
                .setRationaleMessage(getResources().getString(R.string.permission_2))
                .setDeniedMessage(getResources().getString(R.string.permission_1))
                .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
                .check();

    }
}

 

----

 

https://github.com/codingspecialist/Android-Room-Service-ImageUpload-V3

 

codingspecialist/Android-Room-Service-ImageUpload-V3

Contribute to codingspecialist/Android-Room-Service-ImageUpload-V3 development by creating an account on GitHub.

github.com

 

 

 

 

 

 

 

 

전역공간에 버튼 추가

 

 

onCreateView 설정

1. 인플레이터를 이용하여 ViewGroup rootView을 만들어주고

2. 버튼을 rootView에서 찾는다

3. 버튼에 클릭 리스너를 추가하고 Intent intent에 첫번째 인자에 현재 액티비티, 두번째 인자에 이동할 액티비티를 추가고 startActivity(intent)를 하여 이동하게 만든다

4. 무조건 아까 만든 rootView를 리턴해야한다

 

 

 

구조

빨간박스 이외의 메뉴들은 지금 블로깅과 크게 관련이 없습니다.

 

코드설명

 

frag1.xml
frag2.xml

프래그먼트를 2개 만들어 봅니다
아래와 같이 간단히 구분할 수 있을 정도로만 만들어서
첫번째와 두번째 프래그먼트를 만듭니다 .

 

 

 

activity_main.xml

빨간박스 이외의 것은 신경쓰지 않으셔도 됩니다.
리니어 레이아웃으로 구성하여 탭레이아웃과 뷰페이저를 입력합니다.

 

 

 

Frag1
Frag2

프래그먼트는 자바파일도 만들어 줘야합니다.
Fragment를 상속받고
onCreateView를 오버라이딩하여
infalter를 이용하여 뷰를 리턴합니다.

기타설명은 주석 참조

 

 

 

FragmentAdapter

프래그먼트를 이용하려면 프래그먼트 어댑터를 쓰는 것이 좋습니다.
FragmentPagerAdapter 또는 FragmentStatePagerAdapter를 원하는대로 상속합니다.

fragmentList : 프래그먼트를 모두 담는 리스트
addFragment : 프래그먼트에 아이템 추가
getItem : 프래그먼트를 가져오기
getCount : 프래그먼트 개수 확인

 

 

 

MainActivity

TabLayout / ViewPager / FragmentAdapter 를 전역변수에 추가하고
onCreate에서 연결시켜준다

연결시켜줄 때
adapter = new FragmentAdapter(getSupportFragmentManager(),1); 의 주석 확인

addFragment로 어댑터에 프래그먼트를 추가하고

뷰페이저에 어댑터를 연결 + 탭레이아웃에 뷰페이저를 연결

마지막으로 탭레이아웃의 탭이름을 정해준다

 

 

 

결과

 

 

 

코드 확인

https://github.com/jaybon1/androidwork/tree/master/toolbarTest

 

jaybon1/androidwork

Contribute to jaybon1/androidwork development by creating an account on GitHub.

github.com

 

 

구조

Frag와 관련된 것은 신경쓰지 말고 나머지만 확인하자

 

코드 설명

 

build.gradle(Module: app)

머터리얼 라이브러리를 추가한다

 

 

 

styles.xml

기본 액션바를 없애고 툴바를 만들 것이기 때문에 
NoActionBar를 입력하자

 

 

 

toolbar.xml

툴바 역할을 할 페이지를 만든다

렐러티브 레이아웃을 사용하고 넓이는 매치 높이는 직접 조절하였음
텍스트뷰를 하나 만들어서 중앙에 배치하고
(종이 위에 작은 종이를 올리듯이) 이미지뷰를 텍스트뷰 아래에 작성하여 텍스트뷰 보다 위로 오도록 한 뒤
부모의 오른쪽에 붙도록 만들었음

 

 

 

nav_header.xml

툴바의 햄버거 버튼을 누르면 나오는 화면중 헤더를 만든다

레이아웃은 크게 상관 없을듯 하며
화면을 위아래로 채우기위해 height를 매치로 한듯 하다
텍스트 뷰를 이용하여 간단히 헤더를 만들어준다.

 

 

nav_menus.xml

메뉴를 누르면 나올 메뉴들을 작성한다

 

 

 

 

activity_main.xml

화면 위에 서랍처럼 메뉴가 밀려와야 되기 때문에
드로어레이아웃을 사용해준다

그리고 드로어레이아웃 하위에 네비게이션뷰를 만들어 주고
end|right를 이용하여 오른쪽에서 밀려나오도록 한다

또한 네비게이션 뷰 안에

        app:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menus"

위의 코드를 입력하여 헤더와 메뉴들이 나오도록한다

네비게이션뷰 하위에 툴바를 인클루드 시켜준다

 

 

MainActivity

햄버거버튼인 ImageView와
메인레이아웃인 DrawerLayout을 전역변수로설정하고
onCreate에서 xml id로 연결해준다

그리고 imageView에 클릭리스너를 달아줘서
누르면 옆에 있는 것들이 나오도록 한다.

 

 

 

결과

 

 

 

iv_search_

iv_info

iv_infodetail

iv_post

iv_postdetail

iv_slide

iv_join

iv_login

iv_rank_

 

-----------------------------

디버깅 하는 법

 

로그를 먼저 찍고 보는 연습하고 디버그 하자

 

 

 

 

 

 

Step Over 같은 깊이 F8

Step Into 함수안으로 F7

 

sts에서도 라인에 더블클릭하여 디버깅 함

-----------------------

 

https://recipes4dev.tistory.com/154

 

안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)

1. 안드로이드 리사이클러뷰(RecyclerView) 리사이클러뷰(RecyclerView)는, "많은 수의 데이터 집합을, 제한된 영역 내에서 유연하게(flexible) 표시할 수 있도록 만들어주는 위젯"입니다. [안드로이드 개발

recipes4dev.tistory.com

 

mxl 가져오기

https://github.com/codingspecialist/Android-Retrofit2-MovieApp-V1-Design

 

codingspecialist/Android-Retrofit2-MovieApp-V1-Design

Contribute to codingspecialist/Android-Retrofit2-MovieApp-V1-Design development by creating an account on GitHub.

github.com

 

activity_main.xml

더보기
<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <include layout="@layout/toolbar_main"/>

    <include layout="@layout/card_item" />
    <include layout="@layout/card_item" />
    <include layout="@layout/card_item" />
    <include layout="@layout/card_item" />
    <include layout="@layout/card_item" />

    <!--리사이클러뷰 넣어야 함-->

</LinearLayout>

 

card_item.xml

더보기
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

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

        <androidx.cardview.widget.CardView
            android:id="@+id/cardView"
            android:layout_margin="20dp"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            app:cardCornerRadius="10dp"
            app:cardElevation="20dp">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:textStyle="bold"
                    android:text="벤자민 버튼의 시간은 거꾸로 간다."
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="1.0"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

                <TextView
                    android:id="@+id/tv_rating"
                    android:layout_width="29dp"
                    android:layout_height="23dp"
                    android:layout_marginTop="12dp"
                    android:text="9.9"
                    app:layout_constraintEnd_toEndOf="@+id/tv_title"
                    app:layout_constraintTop_toBottomOf="@+id/tv_title" />

                <RatingBar
                    android:id="@+id/rating_bar"
                    style="?android:attr/ratingBarStyleIndicator"
                    android:layout_width="186dp"
                    android:layout_height="36dp"
                    android:layout_marginTop="28dp"
                    android:numStars="5"
                    app:layout_constraintEnd_toEndOf="@+id/tv_rating"
                    app:layout_constraintTop_toTopOf="@+id/tv_rating" />

            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.cardview.widget.CardView>

    </RelativeLayout>

    <RelativeLayout

        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <com.makeramen.roundedimageview.RoundedImageView
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/iv_poster"
            android:src="@drawable/main"
            android:layout_marginLeft="40dp"
            android:layout_marginTop="10dp"
            android:layout_width="110dp"
            android:layout_height="120dp"
            android:scaleType="fitXY"
            app:riv_corner_radius="20dip"
            app:riv_border_width="2dip"
            app:riv_border_color="#FFFFFF"
            app:riv_mutate_background="true"
            app:riv_tile_mode="clamp" />

    </RelativeLayout>


</FrameLayout>

 

toolbar_main.xml

더보기
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
    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:id="@+id/toolbar"
    android:elevation="20dp"
    app:contentInsetStart="0dp">

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

        <ImageView
            android:layout_marginLeft="10dp"
            android:id="@+id/menu_Icon"
            android:layout_width="45dp"
            android:layout_height="40dp"
            android:src="@drawable/ic_dehaze"
            android:scaleType="fitXY"
            android:layout_centerVertical="true"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:textSize="25sp"
            android:textStyle="bold"
            android:text="Movie"
            android:gravity="center_vertical|center_horizontal" />

        <ImageView
            android:layout_marginRight="10dp"
            android:id="@+id/search_icon"
            android:layout_width="45dp"
            android:layout_height="40dp"
            android:src="@drawable/ic_search"
            android:scaleType="fitXY"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"/>

    </RelativeLayout>

</androidx.appcompat.widget.Toolbar>

 

 

https://github.com/codingspecialist/Android-Retrofit2-MovieApp-V2-Complete

 

codingspecialist/Android-Retrofit2-MovieApp-V2-Complete

Contribute to codingspecialist/Android-Retrofit2-MovieApp-V2-Complete development by creating an account on GitHub.

github.com

 

YtsData

더보기
package com.jaybon.movieapp;

import java.util.List;

import lombok.Data;

@Data
public class YtsData {
    private String status;
    private String status_message;
    private MyData data;

    @Data
    public class MyData { // 외부에서 접근할 필요가 있는 것은 public

        private int movie_count;
        private int limit;
        private int page_number;
        private List<Movie> movies;

        @Data
        class Movie{
            private String title;
            private float rating;
            private String medium_cover_image;
        }
    }

}

 

 

레트로핏 서비스(레파지토리) 예시

package com.jaybon.movieapp;

import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;

// YtsService 또는 레파지토리로 만들면된다
public interface YtsService {

    @GET("list_movies.json")
    Call<YtsData> 영화목록가져오기(
            @Query("sort_by") String sort_by,
            @Query("limit") int limit,
            @Query("page") int page
    );

    // onCreate에서 안만들고 여기서 바로 만들어버림
    // 서비스가 많아지면 컨피그 인터페이스 파일 만들어서 세팅
    public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://yts.mx/api/v2/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();
}

 

 

메인액티비티

 

 

 

activity_main.xml

 

YtsAdapter

 

피카소 버전에 따라 with나 get을 사용해야한다

implementation 'com.squareup.picasso:picasso:2.71828'

 

더보기
package com.jaybon.movieapp;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;

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

import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.List;

public class YtsAdapter extends RecyclerView.Adapter<YtsAdapter.MyViewHolder>{

    private static final String TAG = "YtsAdapter";

    // 어댑터는 항상 데이터(컬렉션)을 들고 있어야한다.
    private List<YtsData.MyData.Movie> movies = new ArrayList<>();

    // 아이템을 개별로 넣기
    public void addItem(YtsData.MyData.Movie movie){
        movies.add(movie);
    }

    // 리스트를 바로 넣기
    public void addItems(List<YtsData.MyData.Movie> movies){
        this.movies = movies;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View cardItemView = inflater.inflate(R.layout.card_item, parent, false); // false : 동적
        return new MyViewHolder(cardItemView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.setItem(movies.get(position));
    }

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

    // 책꽂이 (View들을 채워 두면 됨)
    // 뷰가 만들어지려면 card_item이 들고있는 모든 뷰들을 전역변수로 설정해야함
    public static class MyViewHolder extends RecyclerView.ViewHolder {

        private ImageView ivPoster;
        private TextView tvTitle;
        private TextView tvRating;
        private RatingBar ratingBar;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            ivPoster = itemView.findViewById(R.id.iv_poster);
            tvTitle = itemView.findViewById(R.id.tv_title);
            tvRating = itemView.findViewById(R.id.tv_rating);
            ratingBar = itemView.findViewById(R.id.rating_bar);
        }

        public void setItem(YtsData.MyData.Movie movie){
            tvTitle.setText(movie.getTitle());
            tvRating.setText(movie.getRating()+"");
            Picasso.get().load(movie.getMedium_cover_image()).into(ivPoster);
            ratingBar.setRating(movie.getRating()/2);
        }

    }

}

 

MainActivity

더보기
package com.jaybon.movieapp;

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

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Main_Activity";
    private Context mContext = MainActivity.this; // 메인액티비티는 액티비티+xml정보

    private RecyclerView recyclerView;
    private RecyclerView.LayoutManager layoutManager; // 리사이클러뷰는 무조건 이것이 필요하다
    private YtsAdapter adapter;

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

        // 오브젝트 초기화
        init();



        // 다운로드
        initDownload();
        
    }

    private void init(){
        recyclerView = findViewById(R.id.recycler_view);
//        recyclerView.setHasFixedSize(); // 리사이클러뷰 높이

        layoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new YtsAdapter();
    }

    // Yts 데이터 받기
    private void initDownload(){
        YtsService ytsService = YtsService.retrofit.create(YtsService.class);

        // 미래의 데이터를 call에 담기 (퓨처)
        Call<YtsData> call = ytsService.영화목록가져오기("rating",10,1);

        // enqueue가 하는 역할은 응답 받아주는 것 (프로미스의 then 과 같음)
        call.enqueue(new Callback<YtsData>() {
            @Override
            public void onResponse(Call<YtsData> call, Response<YtsData> response) {
                if(response.isSuccessful() == true){
                    YtsData ytsData = response.body();
                    // 리사이클러뷰 어댑터에 연결
                    recyclerView.setAdapter(adapter);
                    adapter.addItems(ytsData.getData().getMovies());

                }
            }

            @Override
            public void onFailure(Call<YtsData> call, Throwable t) { // 에러가 궁금하면 t.를 통해서 확인
                Toast.makeText(MainActivity.this, "다운로드 실패", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

 

이벤트다는법

1. 라이브러리 없이 뷰를 연결

뷰가 만들어지는 곳에서 이벤트 달기

교재 417

 

2.메인adapter에 setItemClicklistener달기

교재 434

 

3. 메인에 리스너

 

 

 

4. 라이브러리

 

 

 

 

 

---------------

디펜던시 정리

----------------

맵리듀스 - 분산 처리

하둡 - 데이터를 분산시켜서 모으기

-------------------

 

 

https://square.github.io/retrofit/

 

Retrofit

A type-safe HTTP client for Android and Java

square.github.io

 

 

아래 페이지에서 레트로핏 강의 듣기

https://codinginflow.com/

 

Tutorials - Coding in Flow

⭐ Newest Tutorials Language Tutorials Kotlin Android Tutorials 🏛️ Architecture & Best Practices Room + ViewModel + LiveData + RecyclerView (MVVM) Dagger 2 View Binding Data Binding Testing 📏 Layout ... Read more

codinginflow.com

 

 

레트로핏은 쓰레드 콜백 다된다

 

 

통신할 때 인터페이스를 만드는데

어노테이션 @GET을 만들어 놓고

함수를 호출하면 해당주소로 요청하여 제이슨으로 받아서 오브젝트로 저장한다

내 레파지토리가 있는 것처럼 가져올 수 있다

 

 

baseUrl에 주소가 있다

 

 

헤더도 붙일 수 있다

 

 

주소에서 변수받기

--------------

 

 

-----------------

 

 

 

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

 

 

버전 적을 때 괄호 삭제

 

 

 

 

오류가난다 (Gson 설치해야함)

 

 

메이븐레파지토리에서 지선 임플리먼테이션

 

 

지선 컨버터 임플리먼테이션

 

 

 

 

------------

Gson 어노테이션 공부하기

-------------

 

 

복붙

androidX가 아니다 주의! 내용만 복사해서 쓰자.

 

 

 

 

 

 

메인액티비티

 

 

 

 

앱 접근권한

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

 

 

에러나면

https://stackoverflow.com/questions/59448845/no-static-method-metafactory

 

No static method metafactory

I have an issue with my app that when I log in, the app crashes and I get the error: java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Str...

stackoverflow.com

 

확인

 

추가

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

 

 

https://yts.mx/api#list_movies

 

API Documentation - YTS YIFY

Official YTS YIFY API documentation. YTS offers free API - an easy way to access the YIFY movies details.

yts.mx

 

 

----

cors 정책 - cross origin

----

 

 

롬복 라이브러리 추가

    compileOnly 'org.projectlombok:lombok:1.18.8'
    annotationProcessor 'org.projectlombok:lombok:1.18.8'

 

 

Yts 모델 생성

더보기
package com.jaybon.retrofit2ex01;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Yts {

    public String status;
    public String statusMessage;
    public Data data;
    public Meta meta;

    @lombok.Data
    public class Data {

        public long movieCount;
        public long limit;
        public long pageNumber;
        public List<Movie> movies = null;

        @lombok.Data
        public class Movie {

            public long id;
            public String url;
            public String imdbCode;
            public String title;
            public String titleEnglish;
            public String titleLong;
            public String slug;
            public long year;
            public double rating;
            public long runtime;
            public List<String> genres = null;
            public String summary;
            public String descriptionFull;
            public String synopsis;
            public String ytTrailerCode;
            public String language;
            public String mpaRating;
            public String backgroundImage;
            public String backgroundImageOriginal;
            public String smallCoverImage;
            public String mediumCoverImage;
            public String largeCoverImage;
            public String state;
            public List<Torrent> torrents = null;
            public String dateUploaded;
            public long dateUploadedUnix;

            @lombok.Data
            public class Torrent {

                public String url;
                public String hash;
                public String quality;
                public String type;
                public long seeds;
                public long peers;
                public String size;
                public long sizeBytes;
                public String dateUploaded;
                public long dateUploadedUnix;

            }

        }

    }

    @lombok.Data
    public class Meta {

        public long serverTime;
        public String serverTimezone;
        public long apiVersion;
        public String executionTime;

    }

}

 

 

JsonPlaceHolderApi

더보기
package com.jaybon.retrofit2ex01;

import java.util.List;
import retrofit2.Call;
import retrofit2.http.GET;
public interface JsonPlaceHolderApi {
    @GET("posts")
    Call<List<Post>> getPosts();

    @GET("list_movies.json&sort_by=rating")
    Call<Yts> getYts();
}

 

 

메인 액티비티

더보기
package com.jaybon.retrofit2ex01;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.List;

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

public class MainActivity extends AppCompatActivity {

    private TextView textViewResult;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textViewResult = findViewById(R.id.text_view_result);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://yts.mx/api/v2/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        JsonPlaceHolderApi jsonPlaceHolderApi = retrofit.create(JsonPlaceHolderApi.class);
        Call<Yts> call = jsonPlaceHolderApi.getYts();
        call.enqueue(new Callback<Yts>() {
            @Override
            public void onResponse(Call<Yts> call, Response<Yts> response) {
                if (!response.isSuccessful()) {
                    textViewResult.setText("Code: " + response.code());
                    return;
                }
                Yts yts = response.body();
                for (Yts.Data.Movie movie : yts.data.movies) {
                    String content = "";
                    content += "ID: " + movie.getId()  + "\n";
                    content += "Title: " + movie.getTitle() + "\n";
                    content += "Summary: " + movie.getSummary() + "\n";
                    content += "img: " + movie.getBackgroundImage() + "\n\n";
                    textViewResult.append(content);
                }
            }
            @Override
            public void onFailure(Call<Yts> call, Throwable t) {
                textViewResult.setText(t.getMessage());
            }
        });
    }
}

 

 

 

 

 

--------------------

 

쿼리스트링 맵으로 넣기

 

 

 

 

 

-------------------

 

스프링 서버에서 데이터가져오기

 

https 요청이 아닌 http 요청시! 주의사항

"CLEARTEXT communication to XXXX not permitted by network security policy"

https://gun0912.tistory.com/80

 

[안드로이드]CLEARTEXT communication to XXXX not permitted by network security policy

"CLEARTEXT communication to XXXX not permitted by network security policy" 어느날 코드를 바꾼게 없는데도 위와 같은 오류가 발생하면서 앱이 실행이 안되는 일이 발생합니다. 그 이유는 여러분 혹은 사용자..

gun0912.tistory.com

 

 

User

더보기
package com.jaybon.retrofit2ex01;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {

    public long id;
    public String username;
    public String password;
    public String email;
    public Object profile;
    public String createDate;

}

 

JsonPlaceHolderApi

더보기
package com.jaybon.retrofit2ex01;

import java.util.List;
import java.util.Map;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;

public interface JsonPlaceHolderApi {
    @GET("posts")
    Call<List<Post>> getPosts();

    @GET("list_movies.json&sort_by=rating")
    Call<Yts> getYts();

    @GET("userdata")
    Call<List<User>> getUsers();
}

 

MainActivity

더보기
package com.jaybon.retrofit2ex01;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

public class MainActivity extends AppCompatActivity {

    private TextView textViewResult;
    private static final String TAG = "Main_Activity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textViewResult = findViewById(R.id.text_view_result);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.0.60:8000/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        JsonPlaceHolderApi jsonPlaceHolderApi = retrofit.create(JsonPlaceHolderApi.class);


        // 쿼리스트링 만들기
        Map<String, String> queryString = new HashMap<>();
        queryString.put("sort_by", "rating");
        queryString.put("page", "2");


        Call<List<User>> call = jsonPlaceHolderApi.getUsers();
        call.enqueue(new Callback<List<User>>() {
            @Override
            public void onResponse(Call<List<User>> call, Response<List<User>> response) {
                Log.d(TAG, "onResponse: "+response.message());
                Log.d(TAG, "onResponse: "+response.headers());

                if (!response.isSuccessful()) {
                    textViewResult.setText("Code: " + response.code());
                    return;
                }
                List<User> users = response.body();
                for (User user : users) {
                    String content = "";
                    content += "ID: " + user.getId()  + "\n";
                    content += "유저명: " + user.getUsername() + "\n";
                    content += "이메일: " + user.getEmail() + "\n\n";
                    textViewResult.append(content);
                }
            }
            @Override
            public void onFailure(Call<List<User>> call, Throwable t) {
                Log.d(TAG, "onFailure: "+t.getMessage());
                textViewResult.setText(t.getMessage());
            }
        });
    }
}

 

 

 

 

-------------------

 

피키소 - 이미지 그대로 - 퀄리티

글라이드 - 이미지 용량 줄여서 - 퍼포먼스

유니버셜이미지로더 

 

https://blog.naver.com/getinthere/221636149736

 

안드로이드 RecyclerView.Adapter를 이용한 RecyclerView 5 (Retrofit2) 구글 Glide 이미지로더

Picasso 대신 Glide 사용하기https://github.com/bumptech/glideGlide는 구글에서 만든 이미지 로더 라...

blog.naver.com

 

자바에서는 동영상이나 이미지 받을 때 힙메모리 영역에 받는다

그래서 받으면서 힙용량을 늘리는 방식을 쓰든 해야한다

jvm의 힙최대영역은 컴퓨터 메모리의 절반이다

 

피카소 의존성

메니페스트 인터넷설정

http 요청 설정

 

 

 

 

사용법

이미지주소와 이미지뷰를 선택만 하면된다

 

 

https://github.com/square/picasso

 

square/picasso

A powerful image downloading and caching library for Android - square/picasso

github.com

 

 

피카소 의존성

implementation 'com.squareup.picasso:picasso:2.71828'

 

 

메니페스트 설정

http 요청 설정

 

 

Picasso.get()는 IO가 일어나기 때문에 쓰레드와 핸들러가 있을 것을 예상해야한다

 

 

 

-----------------------

 

글라이드

 

https://github.com/bumptech/glide

 

bumptech/glide

An image loading and caching library for Android focused on smooth scrolling - bumptech/glide

github.com

 

의존성

  implementation 'com.github.bumptech.glide:glide:4.11.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

 

Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts