페이스북 연동 로그인


마지막 세미나인 8주차 세미나 시간에는 가장 많이 쓰이면서 간단한 방식인 페이스북 연동 로그인을 할 것입니다.
실제로 AppJam을 한다면 SNS 연동 로그인 기능을 많이 사용하기 때문에 페이스북 연동 로그인을 실습할 예정입니다.

Part1. 페이스북 개발자 등록과 앱 ID를 얻기

1. 먼저, Google에 페이스북 개발자를 검색하고 두 번째 항목을 선택합니다.

2. 우측 상단의 내 앱을 클릭합니다.
3. 새 앱 추가 버튼을 클릭합니다.

4. 앱 ID를 작성하고 앱 ID 만들기 버튼을 클릭합니다.

5. 위의 단계를 거치게 되면 다음과 같은 화면이 나옵니다.

6. 여기서 페이스북 로그인에서 설정을 클릭합니다.
7. 아래의 화면에서 iOS, Android, WWW 중에서 사용할 것을 선택합니다. 안드로이드 개발을 위해서 사용하므로 안드로이드를 선택합니다.

8. 1번은 다음을 선택해서 넘어갑니다.

9. 2번도 다음을 선택해서 넘어가도록 합니다.

10. 아래의 패키지 이름에 진행하고 있는 프로젝트의 Manifest 파일에 가서 패키지 이름을 복사한 다음 붙여넣기를 합니다. 기본 액티비티 클래스 이름은 작성하지 않아도 됩니다.

11. 그 다음에 좌측에 설정 - 기본 설정으로 들어간 다음에 하단의 플랫폼 추가를 클릭하여 패키지 이름과 해쉬 키 내용을 작성하고 저정을 누릅니다. 해쉬 키를 얻는 과정은 아래에서 설명하도록 하겠습니다.

12. 그리고 상단의 보이는 앱 ID를 복사한 다음 안드로이드 스튜디오에서 진행하는 프로젝트로 이동합니다.

Part2. 해쉬 키를 얻고 페이스북 로그인을 진행해보자.

1. 먼저, 해쉬 키를 얻기 위해서 코드를 작성합니다. 아래의 코드는 세미나 시간에 파트장님께서 공유해주신 코드로 이 코드를 통해서 고유의 해쉬 키를 얻습니다. (참고로 지금 사용하는 해쉬 키는 나중에 배포할 때 사용하는 릴리즈용 해쉬 키와는 다릅니다. 따라서 배포를 위해서는 다시 릴리즈용 해쉬 키를 발급 받아서 사용해야 합니다. 지금은 배포의 목적이 아니기 때문에 이 해쉬 키를 사용합니다.) 이를 통해서 얻은 해쉬 키를 위의 과정에서 11번에 등록합니다.

1
2
3
4
5
6
7
8
9
10
11
12
try {
val info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
for (signature in info.signatures) {
val md = MessageDigest.getInstance("SHA")
md.update(signature.toByteArray())
Log.d("KeyHash:", Base64.encodeToString(md.digest(), Base64.DEFAULT))
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}

2. gradle의 Project 안에다가 mavenCentral()를 추가합니다.

3. 그 다음은 gradle의 app 안에 dependencies에 아래의 코드를 추가합니다.

1
implementation 'com.facebook.android:facebook-login:[4,5)'

4. 그리고 Manifest 파일에 페이스북 로그인을 위해서 인터넷 접근을 허용해줍니다.

5. 다음으로는 res - valuse - string.xml 파일에 Part1에서 얻은 앱 ID를 가져와서 등록합니다. (나의 앱 ID가 노출되기 때문에 가상의 앱 ID인 000000000000000를 사용하였습니다.)

6. 다음 과정은 Manifest의 application 태그 안에다가 provider, meta-data, activity를 추가해줍니다.

7. MainActivity에 버튼을 하나 추가하고 다음 코드를 작성한 뒤 실행을 하면 페이스북 로그인이 되는 것을 확인할 수 있습니다. 그리고 Log를 찍어 확인해보면 사용자의 정보를 페이스북으로부터 받아온 것을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class MainActivity : AppCompatActivity() {

lateinit var callbackManager: CallbackManager
// 커스텀을 이용한 로그인 버튼 생성
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

callbackManager = CallbackManager.Factory.create() // 콜백 매니저 생성


main_fb_login_btn.setOnClickListener {
loginWithFaceBook()
}

}


// facebook로 로그인한다는 함수
fun loginWithFaceBook(){

LoginManager.getInstance().logInWithReadPermissions(this@MainActivity,
Arrays.asList("public_profile", "email"))
// profile과 email 정보를 가지고 오겠다.
LoginManager.getInstance().registerCallback(callbackManager, object : FacebookCallback<LoginResult> {
// 콜백을 등록하겠다.
override fun onSuccess(loginResult: LoginResult) {
val request: GraphRequest
request = GraphRequest.newMeRequest(loginResult.accessToken) { user, response ->
if (response.error != null) {

} else {
Log.i("TAG", "user: " + user.toString())
Log.i("TAG", "AccessToken: " + loginResult.accessToken.token)
// 유효 토큰값
Log.i("TAG", "AccessToken: " + loginResult.accessToken.userId)


/*FIXME
* 토큰값이나 userId로 사용자를 구분하는데
* 토큰 보다는 userId로 사용자를 구분하는것이 좋다. 왜냐하면 토큰은 만료 기간이 있기 때문에(짧지는 않지만)
* 그래서 userId를 서버에 보내서 로그인하는 것이 좋다.
*
* 로그인을 성공해서 이 안으로 들어오면 받은 id(숫자로만 구성된것)를 서버에게 보낸다. (request)
* 그러면 로그인에 대한 response가 돌아올 것!
*
* 근데, accessToken을 보내면 이것은 수시로 바뀌기 때문에 이것을 서버에게 보내주게 되면 서버가 처음에 우리가 보낸 토큰을
* 디비에 저장하지만 accessToken이 변하게 되면 디비에 없는 경우가 발생하게 된다.
* 그래서 웬만하면 id를 보내는 방향으로 하는 것이 좋다.(서버와 말을 잘해서^^)
*
* */

setResult(Activity.RESULT_OK)

}
}
val parameters = Bundle()
parameters.putString("fields", "id,name,email,gender,birthday")
request.parameters = parameters
request.executeAsync()
}

override fun onCancel() {

}

override fun onError(error: FacebookException) {

}
})
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
//super.onActivityResult(requestCode, resultCode, data)
// 여기서는 메니페스트에 등록한 페이스북 로그인 액티비티를 부르고 여기서 필요한 정보들을 onActivityResult를 통해서 받아오게 된다.

callbackManager.onActivityResult(requestCode, resultCode, data)
}
}

Git


주의할 점

Git은 Crash가 많이 나서 즉, 충돌이 많이 발생한다…ㅜㅜ
애초에 Crash가 나지 않는 방법으로 깃을 사용하는 것이 좋다.

init : 해당 프로젝트에 git을 사용함. init과 동시에 Git은 로컬에 저장소를 생성해준다. (세이브 폴더)
commit : 로컬 저장소로 변경된 내용을 보낸다.(세이브) 그러면 로컬 저장소에 내가 추가한 내용이 저장되게 된다. 또한, 새로 추가되는 내용뿐만 아니라, 변경사항이 있다면 변경됨

문제점

저장소(세이브 파일)이 한개라는 문제가 발생한다. 예를 들어서 실수로 잘못 저장했을 때 돌아갈 수 없다. 또한, 저장 이력을 볼 수 없다.

이를 해결하기 위한 방법 중 하나로 브랜치를 사용한다.
브랜치 : 새로운 저장공간을 만듦
마스터 브랜치 : 중앙 저장소, 모든 브랜치는 결국 중앙저장소에 저장되어야 반영된다. (Android의 Main Thread 개념)
그냥 브랜치 : 추가 저장소, 모든 브랜치는 작업 완료 후, 마스터 브랜치에 합쳐져야 한다.
merge : 브랜치를 합병한다.(세이브 파일을 합침)

Github : 온라인 저장소(원격, remote)를 이용할 수 있도록 해주는 서비스
push : 로컬 저장소를 원격 저장소에 올림
pull : 내가 안전하다 싶으면 충돌이 날 거 같지 않으면 pull로 합침
fetch : 가져오기만 함. 그리고 그 이력을 보고 내가 수동으로 합침
clone : 원격 저장소를 내 컴퓨터에 복사함

pull, fetch는 local 저장소와 remote 저장소가 연동이 되어 있어야 한다. 하지만, clone는 연동이 되어있지 않아도 된다.

TIP

  1. 작업을 먼저 하기 전에 pull을 한 다음에
  2. 자신이 할 작업을 한다.
  3. 퇴근 전 commit
  4. 출근 전 pull
  5. 작업 영역에서 남의 영역을 건드리지 말자!
  6. 혹시나 건드릴 것이라면 건드릴 작업영역이 같아지는 다음에 건드리자!!
  • .gitignore

.gitignore 파일을 만들어서 그 안에 올릴 파일과 올리지 않을 파일을 명시할 수 있다. AppJam을 할 때 Main 개발자는 저장소를 만들고 Sub 개발자들은 Fork를 한다. 그리고 Main 개발자가 .gitignore 파일을 작성할 때 무시할 파일을 명시하고 Sub 개발자는 올릴 파일을 명시한다.

이렇게 하면 서로의 영역을 침범하지 않아 충돌이 일어나는 경우를 최소화 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
/* -> 모든 파일을 올리지 않는다.

!/abc.txt -> 그 중에서 abc.txt는 빼고 즉, abc.txt는 올리겠다는 의미!

폴더링은 항상 해야 한다.

서브 개발자가 만약 api를 작업한다면
api만 변경된 내용을 올려야 하기 때문에

/*

!/api/* -> api 폴더는 올리겠다는 의미

위 처럼 하게 된다면 모든 파일을 올리지 않을 것이지만, api 폴더 밑에 있는 파일들은 제외하겠다. 즉, api 폴더는 올리겠다는 의미!!
폴더링 잘하고 .gitignore를 잘하면 된다. --> 중요!!!

Comment and share

지금까지의 UI


SOPT를 통해서 UI를 구현하는 법에서부터 기능까지 많은 것을 배울 수 있었다. 하지만 짧은 시간 안에 진행하는 세미나에서 많은 것을 얻기란 쉽지 않은 법!

지금까지 구현한 UI정적이다. 즉, 클릭 클릭 하면 끝나게 되는 것이다. 반면에 우리가 실제로 사용하는 앱(Facebook, Youtube 등)를 보면 스크롤, 로딩, 알람, 로딩 등등 다양하고 적절한 애니메이션이 존재한다.

다만, 동작 하나를 구현하기 위해서 필요한 코드는 생각 외로 복잡하고 버그나 에러의 발생 원인을 찾기가 어렵다. 하지만, 완성하면 통신 그 이상의 뿌듯함을 얻을 수 있다고 한다. (우리의 파트장 영규님의 말씀^^)

그래서 이번 세미나의 목표는 카카오톡에서 채팅방을 좌우로 스와이프 했을 때의 Animation을 흉내 내보고자 합니다.

애니메이션 구현은 3차 세미나에서 추가적으로 기능을 구현하였습니다. 구현한 코드는 아래의 제 Github 주소에서 확인할 수 있습니다.

Github : https://github.com/WooVictory/SOPT_22th_Seminar3_HomeWork

Comment and share

이번주에는 내부 DB, Realm, SQLite에 대해 배워보겠습니다.

내부 DB


안드로이드 앱 개발을 하다보면 서버와 통신을 하면서 가끔 이런 생각을 하곤 합니다.

데이터가 어딘가에 저장되어 있어야 하는 건 맞고 서버랑 통신을 하긴 하는데, 굳이 이런 데이터까지 서버랑 통신을 해야하나… 혹은 오히려 서버랑 통신하면 더 불편할 정도로 정말 너무나도 간단한 데이터를 다루거나 서버한테 데이터를 맡기는 것보다는 내가 처리하는게 낫겠다라는 생각을 해본 적이 있습니다…(서버 디스는 아닙니다…^0^)

SharedPreference


  • 가장 간단하게 다룰 수 있는 데이터 저장 방식
  • 자동 로그인을 사용할 때 특히 많이 쓰임
  • 어플이 종료되어도 저장된 데이터는 사라지지 않음
  • 어플이 삭제되면 저장된 데이터는 증발
  • 요청한 데이터가 없을 때 기본값을 지정할 수 있음
  • 쿼리문이 없다.

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
// SharedPreference에 저장
fun setId(context : Context, id : String){
val pref = context.getSharedPreferences(USER, Context.MODE_PRIVATE)
val editor = pref.edit()
editor.putString(ID,id)
editor.commit()
}

// SharedPreference에서 가져오기
fun getId(context : Context) : String{
val pref = context.getSharedPreferences(USER, Context.MODE_PRIVATE)
return pref.getString(ID, "")
}

Realm


  • 최근 각광받는 Mobile Database
  • 쿼리문을 사용하지 않음
  • 객체들로 하여금 데이터를 다룸
  • 어플이 종료되어도 데이터는 유지
  • 어플이 삭제되면 데이터는 증발
  • 일단 DB처럼 Primary Key를 필수로 지정해야 함
  • 데이터가 만일

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

// 데이터 저장 객체 생성
open class MemberVO : RealmObject(){
@PrimaryKey
var id : String = ""
var pwd : String = ""
}

// Realm 초기화
fun init(){
Realm.init(this)
memberRealm = Realm.getDefaultInstance()
}

// 데이터 가져오기
fun getMemberList(id : String) : RealmResults<MemberVO>{
return memberRealm.where(MemberVO::class.java)
.equalTo("id",id).findAll()
}

// 데이터 삽입
fun insertMemberList(){
memberVO = MemberVO()
memberVO.id = sign_id_edit.text.toString()
memberVO.pwd = sign_pw_edit.text.toString()

memberRealm.beginTransaction()
memberRealm.copyToRealm(memberVO)
memberRealm.commitTransaction()
}

// 데이터 삭제
fun deletePokemonList(name : String){
val result = pokemonRealm.where(PokemonVO::class.java)
.equalTo("name",name)
.findAll()

if(result.isEmpty()){
return
}

pokemonRealm.beginTransaction()
result.deleteAllFromRealm()
pokemonRealm.commitTransaction()
}

별도의 과제는 없고 내가 우아한 테크 캠프 코딩 테스트를 보느라 출석만 하고 바로 빠져나오는 바람에 세미나를 듣지 못하였다. 일단은 혼자 자료를 보면서 정리하였는데, 얼른 프로젝트를 보면서 혼자 만들어보면서 공부해야겠다.

공부한 다음에 Github에 코드를 올릴 예정이다.

Comment and share

스플래쉬 + 폰트 + 아이콘


  1. 스플래쉬(Splash)

  • 백그라운드에 있지 않은 어플리케이션을 실행했을 때 맨 처음 나오는 화면
    (보통은 로그인 하기 전에 나오는 화면)을 말한다.
  • 사용자로 하여금 앱이 실행되고 있음을 보여준다.
  • 로그인 전에 서버와 통신해야 할 부분이나 기타 처리할 데이터가 있으면 이 화면에서 처리한다.
  1. 폰트
  • 외부 폰트를 안드로이드 스튜디오 내에서 사용할 수 있도록 xml로 작성한다.
  • res -> new -> Android Resource Directroy에서 font를 추가
  • 사용할 폰트를 font 폴더에 추가한다.
  • 그리고 추가한 폰트를 사용하는 xml 파일을 작성한다.
  • 그리고 layout xml 파일에서 사용할 폰트 xml 파일을 적용한다.
  1. 아이콘

우리가 사용하는 앱의 아이콘은 어떻게 설정할까요??

  • 그렇게 어렵지 않습니다.
  • res -> mimmap -> new -> Image Asset에서 새로운 아이콘 이미지 추가
    Path에서 사용하고 싶은 이미지를 불러오고 적용 시키면 끝!!

화면의 이해


  1. 해상도 : 하나의 이미지를 표혀하는데 몇 개의 픽셀(pixel) 또는 도트(dot)로 나타내는 지의 정도를 알려주는 지표
  • 안드로이드 운영체제를 가진 device는 다양한 종류가 존재한다. 즉 너무나도 다양한 해상도가 존재하는 것이다. 이처럼 다양한 해상도를 가진 device에 뷰를 맞춰서 개발하는 것은 힘든 일이다.
  • 이와 같은 문제를 잘 해결하기 위해서 다음에서 설명하는 개념들을 잘 알고 있어야 한다.
  1. 픽셀의 이해
  • 픽셀은 고정값이다.
  • 그래서 크기가 변한다.
  • 값은 고정이지만 해상도별로 달라보이는 문제가 발생한다.
  • 그래서 값은 고정이어도 크기는 변한다.
  • 그래서 픽셀로는 화면에 대응하기가 힘들다.
  • 픽셀 : 기기 대응이 힘들다.
  1. DP
  • dp는 디바이스에 따라 값이 변한다.
  • 그래서 크기가 고정된다.
  • 기기 해상도에 따라 네모 사이즈가 같아보인다.
    (디바이스 해상도에 따라 크기의 사이즈가 조정되었기 때문)
  • 그래서 device는 달라도 크기는 고정된다.
  • 이 때문에 디자이너가 여러 dp에 대응하게끔 이미지를 준다.

문제

  • 같은 dp라 할지라도 화면 너비, 높이에 따라 차지하는 정도는 달라질 수 있다.즉, 여백 문제가 발생함
  • 크기 중심으로 갈 지 여백 중심으로 갈 지 잘 판단해야 한다.
  • 우선적으로는 사용할 대표 기기를 선정하는 것이 그나마 수치의 오차를 줄일 수 있는 방법
  • 사실상, 가장 확실한 방법은 기기마다 레이아웃을 짜는 것이지만, 쉽지는 않다.
  1. SP
  • 글자에 많이 쓰이는 사이즈 단위
  • 글자가 가지는 고유의 사이즈에 비례해서 크기가 변하는 것
  • 기본 원리는 dp와 유사
  • 사용자가 설정한 글꼴에 따라 호환되어 사이즈 조정

디자이너와 협동하기


방법

  1. 서로가 고생하기

디자이너는 모든 기기에 대한 디자인을 만든다
개발자는 모든 기기에 대한 레이아웃을 짠다.

가능할까?
Build 클래스를 통해 기기 분류가 가능하다. 가장 정확한 방법이지만, SOPT에서 진행하는 2주간의 App Jam에서는 시간이 부족하고 불가능한 방법이다.

  1. 적당히 타협하기
  • 중요도를 묻는다.
    뷰 자체의 크기가 중요하면 뷰 위주로 값을 넣을 것
    배치 및 공간이 중요하면 여백 위주로 값을 넣을 것
  • 기기들의 태생적 한계를 공유한다.
    – 기기가 너무 다양한 나머지 디자인적인 차이와 한계가 존재한다.
    – 디자이너가 기준으로 정한 기기 이외의 기기들과는 보이는 모습이 조금 달라 질 수 있음을 충분히 인지시키자.
  • 수치를 최대한 자세하게 요구한다.
    – 길이든 색상이든 일단 최대한 빈틈없이 알려달라 할 것
    – 수치를 보면 무엇이 중요한지(자체 크기, 공간) 어느정도 판단 가능
  • 이미지 이름은 꼭 소문자, 밑줄(_) 그리고 숫자 조합지어달라 한다.

과제

  • 세미나 시간 동안 디자이너와 협업한 View 완성하기(SeekBar 사용)
  • 참고 : 기능은 구현하지 않고 View 만 구현하였습니다.

Github : https://github.com/WooVictory/SOPT_22th_Seminar5_HomeWork

  • 다른 뮤직 플레이어 (Progressbar Wheel 사용)

Github : https://github.com/WooVictory/SOPT_22th_Seminar5_Preview

Comment and share

통신의 기본

HTTP?

  • 하이퍼텍스트 기반(링크 기반)으로 데이터를 주고 받는다.
  • 인터넷 상에서 데이터를 주고 받기 위한 Client/Server 모델을 따르는 프로토콜
  • 가장 성공적으로 평가받는 프로토콜

프로토콜

Client/Server

주요 메소드

  • GET
    : 데이터 받기
    : URL 혹은 URL에 변수를 포함시켜(포함시키지 않을 수도 있음) 데이터를 받는 방식
  • POST
    : 데이터 생성
    : 데이터를 Body(일종의 객체)에 감싸서 보내는 방식
  • PUT
    : 데이터 업데이트
    : POST와 마찬가지로 Body에 감싸서 보내는 방식
    : 성공적으로 보내면 데이터가 업데이트 된다.
  • DELETE
    : 데이터 삭제
    : 성공적으로 보내면 데이터가 삭제 된다.

In Android

  • Http 기반 REST API(GET,POST,PUT,DELETE)를 Retrofit2 라이브러리를 통해 사용
  • REST API의 표현식인 JSONObject 개념으로 바꿔주는 GSON 라이브러리를 함께 사용

Retrofit2

Retrofit2이란?

  • Http REST API 통신을 위한 라이브러리
  • @ annotation 형식을 통해 사용
  • 인터페이스를 통해 쉽게 구현 할 수 있다.
  • JSON을 간단하게 처리할 수 있다.

JSON/GSON

  1. JSON
  • JSON이란 ‘Key’:value 조합을 갖는 데이터를 말한다.
  • value에는 숫자, 문자열, 배열, 논리, 객체가 올 수 있다.
  • JSON 자체를 배열로 받을 수 있다.
  • 객체중괄호로 {} 구분하고, 배열대괄호로 [] 구분한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"JSON Object" : {
"name" : "Lee",
"age" : 25,
"part" : android,
"etc" : {
"dog" : false,
"cat" : false
}
}

// JSON Object

"JSON Array" : [
{
"name" : "탁형민",
"nick" : "탁클라운"
"age" : 25
},
{
"name" : "류수한",
"nick" : "류뚱"
"age" : 25
},
{
"name" : "이종찬",
"nick" : "종팡"
"age" : 26
},
]

// JSON Array
  1. GSON
1
2
3
4
5
"JSON Object" : {
"name" : "Lee",
"age" : 25,
"part" : android
}
  • 위의 JSON을 아래의 객체로 사용할 수 있게끔 변환해주는 라이브러리
1
2
3
4
5
data class JSON{
var name : String,
var age : Int,
var part : String
}
  • JSON의 Key와 객체의 변수명은 같아야 한다.
  • 물론 타입도 맞춰줘야 한다.

Glide

  • 이미지 로드 라이브러리
  • 서버에서 받은 이미지 url을 로드할 수 있게끔 도와준다.
  • 이미지 비율에 맞게 사이즈 조정
  • Out of memory 방지
  • 기본 사용법
1
2
3
4

Glide.with(Context)
.load(서버에서 받아 온 이미지 url)
.into(이미지를 받을 뷰 객체)

과제

  1. 복습 : 세미나 시간동안 구현한 것 완성 + 상세보기 화면 구현
  2. 응용 : 상세보기 화면에서 댓글 구현(댓글 달기, 삭제)
  3. 심화 : 서버 API가 없으므로 간단한 로그인 & 회원 가입 기능 구현(SharedPreference 사용)

Github : https://github.com/WooVictory/SOPT_22th_Seminar4_HomeWork

Comment and share

RecyclerView

  • 특징
    반복되는 패턴 적용에 유리한 View(일종의 리스트)
    LayoutManager 속성을 추가하여 리스트 타입을 쉽게 변경 할 수 있다.
    재활용성이 뛰어난 구조

구성 요소

DataClass
ViewHolder
LayoutManager
Adapter

DataClass

  • 위의 네모난 채팅방을 item이라고 하며, 이 item에 들어갈 **데이터(자료형)**가 무엇인지 명시
  • 프로필 이미지(Int), 채팅방 이름(String), 미리보기(String), 날짜(String)이 표시 된다.

  • 위 서버의 Response를 보고 작성한 DataClass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
data class GetBoardResponse(
var message : String,
var data : ArrayList<GetBoardResponseData>
)

class GetBoardResponseData(
var board_idx : Int,
var board_title : String?,
var board_content : String,
var board_view : Int,
var board_photo : String?,
var board_writetime : String?,
var user_id : String
)
  • 서버 API를 보고 DataClass를 작성할 경우에는 서버의 Key값과 동일하게 변수명을 작성하여야 한다.

ViewHolder

  • Item에 들어갈 View가 무엇인지 명시
  • 프로필 이미지(ImageView), 채팅방 이름(TextView), 미리보기((TextView), 날짜((TextView)

LayoutManger

  • Item들의 배치 방식을 결정
  • LinearLayoutManager(선형), StaggeredGridLayoutManager(지그재그, 정확히는 가변 그리드), GridLayoutManager(사진첩 형태)
  • 이번 세미나에서는 LinearLayoutManager를 통해서 수직 배치를 할 예정!

Adapter

  • DataClassViewHolder를 연결
  • 이번 세미나에서는 DataClass의 프로필 이미지(Int)을 ViewHolder의 프로필 ImageView에, 채팅방(String)을 채팅방 TextView에, 미리보기(String)을 미리보기 TextView에 연결, 날짜(String)를 날짜 TextView에 연결한다.
  • 서버와 통신을 할 경우에는 DataClass를 서버의 Response(응답)을 보고 작성하게 되는데, DataClass가 서버의 응답을 받아서 ViewHolder와 연결을 해주면 된다!!
  • DataClass가 중요한 부분!!
  • 연결 후 RecyclerView가 Adapter를 잡게 함으로써 사용자에게 보여준다.

과제

  1. 복습 : RecyclerView를 이용한 리스트 만들기 [hint : color값 넘기기!]
  2. 응용 : Item 요소 중에서 '프로필 이미지’를 클릭하면 프뢸 이미지만 볼 수 있는 Activity로 넘어가도록 작성 [hint : Adapter의 ViewHolder를 변경하면 됨]
    [주의 : Item을 클릭하면 상대방과의 채팅방으로 이동하는 Activity와는 별개로 작성해야 함]
  3. 심화 : RecyclerView의 Header를 만들어서 다른 형태의 Item도 RecyclerView에 포함되도록 작성

Github : https://github.com/WooVictory/SOPT_22th_Seminar3_HomeWork

Comment and share

안드로이드의 4대 컴포넌트

Activity(액티비티)

사용자 인터페이스 화면을 가지며 특정한 작업을 담당하는 컴포넌트
UI를 구성하는 컴포넌트
안드로이드의 Manifest 파일에 등록이 되어 있어야 한다.
안드로이드 Application은 최소 1개 이상의 Activity가 있어야 한다.
하나 이상의 View를 가질 수 있다.
쉽게 말하자면 사용자가 보는 화면이다.

Service(서비스)

백그라운드에서 실행되는 컴포넌트로 오랫동안 실행되는 작업이나 원격 프로세스를 위한 작업을 할 때 사용된다.
모든 서비스는 Service 클래스를 상속 받는다.
한 번 실행되면 다른 어플리케이션으로 이동하더라도 종료되지 않는다.
네트워크를 통하여 데이터를 꺼내올 수도 있다.
ex) 뮤직 플레이어, 안드로이드 사용량 모니터링, 주기적으로 특정 웹사이트에서 데이터를 읽어오는 것 등등

BroadCast Receiver

BroadCast는 시스템에서 발생하는 이벤트이며 이것의 Receiver은 특정 BroadCast에 반응하는 컴포넌트
단말기에서 발생하는 다양한 이벤트 / 정보를 받고 반응한다.
수신기를 통해 상황을 감지하고 적절한 작업을 수행한다.
신호만 기다릴 뿐 별도의 UI를 거치지 않는다.
ex) 배터리 부족, 네트워크 전송, 시스템 부팅, 전화/문자 수신, 네트워크 끊김을 알려주는 것 등등

Content Provider

데이터를 관리하고 어플리케이션 간 데이터 공유를 위한 컴포넌트
데이터는 파일 시스템이나 SQLite 데이터베이스, 웹 상에 저장될 수 있습니다.
콘텐트 제공자를 통해서 다른 애플리케이션의 데이터를 쿼리하거나 변경 가능합니다.
유일한 합법적 데이터 공유 장치
(주소록 Content Provider를 통해 제공)

Activity의 생명주기

onCreate()

  • 액티비티가 만들어지고 나서 최초에 실행될 때 호출됩니다.
  • 액티비티에 필요한 리소스들을 초기화하거나 화면에 보이는 뷰들의 일반적인 상태들을 여기서 설정합니다.
  • 이전 상태가 저장되어 있을 경우, 번들 객체를 참조하여 이전 상태로 복원이 가능합니다.
  • 이 다음에는 항상 onStart()가 호출됩니다.

onStart()

  • 액티비티가 화면에 보이기 바로 전에 호출됩니다.
  • 액티비티가 화면상에 보이면 이 메소드 다음에 onResume()메소드가 호출된 것입니다.
  • 매우 빠르게 끝나고, onResume() 단계로 넘어갑니다.
  • 보통 Broadcast Receiver는 이 단계에서 등록하는 것이 좋습니다.
  • 액티비티가 시작되기 전에 호출되는 함수 / 액티비티가 멈춘 후 다시 시작되기 전에 호출되는 함수

onResume()

  • 액티비티가 사용자와 상호 작용하기 바로 직전에 호출되는 함수입니다. 이 메소드가 호출되고 나서 바로 액티비티가 사용자에게 보이기 시작합니다.
  • 액티비티가 화면에 보여집니다.
  • 사용자에게 focus를 잡은 상태입니다.

onRestart()

  • 액티비티가 중지된(Stopped) 이후에 호출되는 메소드로 다시 시작되기 바로 전에 호출됩니다.
  • 액티비티가 멈춰 있다가 다시 다시 호출될 때 불리는 함수입니다.

다른 액티비티가 호출되는 경우

onPause()

  • 액티비티 위에 액티비티가 올라와서 focus를 잃었을 때 호출되는 함수
  • 액티비티가 완전히 가려지지 않고 부분만 가려진 상태에서 호출되는 함수, 즉 일부분이 보이거나 투명상태일 경우에 호출됩니다.
  • 다른 액티비티가 호출되기 전에 실행되기 때문에 onPause() 함수에서 시간이 많이 소요되는 작업이나 많은 일을 처리하면 다른 액티비티가 호출되는 시간이 지연되기 때문에 많은 일을 처리하지 않도록 주의합니다.
  • 영구적인 Data는 여기서 저장합니다.
  • 이 메소드가 리턴하기 전에는 다음 액티비티가 시작될 수 없으므로 이 작업은 빠르게 리턴되어야 합니다.
  • 액티비티가 이 상테에 들어가면 시스템은 액티비티를 강제 종료할 수 있습니다.

onStop()

  • 액티비티 위에 다른 액티비티가 완전히 올라와 100% 가려질 때 호출되는 함수
  • 액티비티가 사용자에게 더 이상 보이지 않을 때 호출되는 함수
  • 만약 이 상태에서 액티비티가 다시 불려지면 onRestart()함수가 호출됩니다.
  • 시스템이 onStop() 상태에 들어오면, onSaveInstanceState()이라는 메소드를 호출하는데, 이 단계에서는 key-value 형태로 값을 저장할 수 있고, 이 값을 onCreate()에서 복원할 수 있습니다.
  • ex) 홈 키를 누른 경우, 또는 다른 액티비티 페이지로 이동이 있는 경우에 호출됩니다.

onDestroy()

  • 액티비티가 완전히 스택에서 없어질 때 호출되는 함수
  • 제거되는 경우입니다.
  • Finish() 메소드가 호출되거나 시스템 메모리 확보를 위해서 시스템이 강제로 종료시키는 경웨 호출될 수 있습니다.

참고

  • onStop(), onDestroy() 함수는 호출되지 않을 수도 있습니다.
  • 메모리 부족으로 인해 onStop()이 호출되지 않을 수 있습니다.

Fragment(프래그먼트)

일종의 하위 Activity이며 자체 생명주기를 갖는다.
자체 입력 이벤트를 가지고 있으며 값 전달은 Bundle로 한다.
[Activity가 Intent로 한 것과는 대조적이다.]
Tag를 달 수 있다.
FragmentManager를 통해 관리할 수 있다.

FragmentManager

Fragment 관리를 목적으로 하는 클래스
FragmentManager 클래스를 통해 FragmentTransaction 객체를 FragmentTransaction을 통해 동적 관리
Kotlin에서는 supportFragmentManager를 통해 생성

FragmentTransaction

Fragment 추가, 삭제, 전환을 도와주는 클래스
addToBackStack() 함수를 호출해서 교체되서 Fragment를 back stack에 저장
항상 마지막에 변경사항 적용을 위해 commit()을 해야 한다.

onAttach() :

  • 프래그먼트가 액티비티에 붙을 때 호출된다.
  • 하지만, 아직 완벽하게 생성된 것은 아니다.
  • 프래그먼트는 액티비티 없이는 단독적으로 존재할 수 없기 때문이다.

onCreate() :

  • 본격적으로 프래그먼트가 액티비티의 호출을 받아 생성되는 시점이다.
  • 액티비티의 onCreate()에서는 view나 UI 작업을 할 수 있으나, 프래그먼트의 onCreate()에서는 할 수 없다.

onCreateView() :

  • 프래그먼트에 속한 각종 view나 viewGroup에 대한 UI 바인딩 작업을 할 수 있다.
  • (Layout을 inflate하여 view 작업을 수행한다.)

onActivityCreated() :

  • 액티비티에서 프래그먼트를 모두 생성하고 난 다음에 호출된다.
  • 즉 액티비티에서 onCreate() 다음에 호출되는 메소드이다.
  • 액티비티와 프래그먼트가 드디어 연결되는 시점이다.

onStart() :

  • 프래그먼트가 사용자에게 보여지기 전에 호출되는 함수
  • (액티비티와 같음)

onResume() :

  • 프래그먼트가 비로소 화면에 보이는 단계이다.
  • 사용자에게 focus를 잡은 상태
  • 사용자와의 상호작용이 가능

다른 프래그먼트가 치고 들어오는 경우(add되는 경우)

onPause() :

  • 프래그먼트는 사용자와의 상호작용을 중지한다.
  • 다시 돌아온다는 보장이 없으므로, 중요한 정보는 저장해둔다.

onStop() :

  • 프래그먼트는 더 이상 보여지지 않게 되며, 프래그먼트 기능은 중지한다.

onDestroyView() :

  • 프래그먼트를 view에서 제거한다.
  • Backstack을 사용했다면, 다시 해당 프래그먼트로 돌아올 때 onCreateView()가 호출된다.

onDestroy() :

  • 프래그먼트를 제거하기 직전이다.

onDetach() :

  • 프래그먼트를 비로소 제거하고 액티비티와의 연결도 해제한다.

과제

  1. 복습 : 세미나 시간동안 구현한 것 완성
  2. 응용 : Fragment안에 Fragment 구현
  3. 심화 : 첫 Fragment에서 이미지가 여러장일 경우에는 swipe를 통해 구현

Github : https://github.com/WooVictory/SOPT_22th_Seminar2_HomeWork

Comment and share

Android 가볍게 보는 특징

  • Google에서 만든 모바일 운영체제
  • 가장 많이 사용하는 OS
  • 오픈소스
  • 다양한 라이브러리 지원
  • 개발 접근이 용이
  • 빈번한 SDK 업그레이드

최근 이슈

최근에 구글과 오라클의 소송에서 구글이 패배함으로써 자바가 위협을 받고 Kotlin이 대두되고 있습니다. SOPT에서는 Kotlin을 이용하여 안드로이드 개발을 배우고 세미나를 진행합니다.

Kotlin의 특징

  • 자동 형 변환 기능을 허용하지 않는다. [타입이 다르면 반드시 형 변환을 해야 한다.]
  • NPE에 대응 [NPE = Null Pointer Exception]
  • 뛰어난 자바와의 호환성
  • 표현이 자바에 비해 훨씬 간결
  • 문장 끝에 세미콜론을 사용하지 않는다.

View / Layout이란??

View와 ViewGroup

  • View
    버튼, 텍스트, 이미지 등 모든 유저 인터페이스(UI)의 통칭
    사용자 눈에 보이는 화면 구성 요소
    Activity 상에서 직접 Code를 작성해서 구현할 수 있지만 더 쉽고 정교하게 작성하기 위해서 XML사용

  • layout_width -> 뷰의 너비(가로 길이)
  • layout_height -> 뷰의 높이(세로 길이)
  • wrap_content -> 내용의 길이(혹은 크기)에 맞게 조절
  • match_parent -> 부모 뷰에 꽉 채워진 크기(여백 제외)
    위의 두 가지가 아닌 경우에는 직접 설정
  • 배경 지정 / 뿐만 아니라 이미지도 지정 가능
  • 화면에 보여지는 속성
  1. visible : 공간도 차지하고 화면에 보임
  2. invisible : 공간은 차지하는데 보이지는 않음
  3. gone : 공간도 차지하지 않고 보이지도 않음

  • View여백 설정
  • margin -> 부모 혹은 View와의 여백
  • padding -> View 외곽과 내용 사이의 여백

ViewGroup

  • 뷰들을 여러 개 포함하고 있는 것
  • 뷰 그룹도 뷰에서 상속하여 뷰가 된다. 즉, 위의 뷰는 버튼, 텍스트 뿐만 아니라 이것들을 포함하는 눈에 보이지 않는 영역을 포함

위젯
뷰 중에서 일반적인 컨트롤 역할을 하고 있는 것
ex) 버튼, 텍스트뷰 등등

Layout

View들이 배치 될 그룹
Layout에 따라 배치되는 방식이 달라짐
XML을 통해 작성
LinearLayout, RelativeLayout, FrameLayout, ConstraintLayout 등등 존재

레이아웃은 그 안에 다른 뷰들을 담을 수 있는데, 레이아웃도 뷰를 상속하여 정의되었기 때문에 레이아웃 안에 다른 레이아웃을 담을 수 있다. 이를 이용하여 복잡한 화면 구성을 할 수 있다.

LinearLayout

  • View들의 선형 배치를 제공하는 Layout
  • 수직 방향, 수평 방향으로 배치 가능
  • 수직 배치시 다음 뷰는 가장 아래 뷰를 기준으로 밑에
    수평 배치시 다음 뷰는 가장 오른쪽 뷰를 기준으로 오른쪽에 배치
  • 배치 기준이 정해져 있어서 직접 정해주지 않아도 됨
  • 특정 뷰 기준으로 배치하는 것은 불가능하다.
  • 방향성이 존재하기 때문에 뷰 위에 뷰를 쌓는 것이 불가능하다.
  • 계단식으로 배치할 때 용이하게 사용할 수 있다.

  • orientation : 뷰 배치에 방향성을 부여한다.(horizontal, vertical)
  • layout_gravity : 부모 컨테이너 안에서 뷰의 위치를 정함
  • gravity : 뷰 안에서 내용물의 위치를 정함
  • layout_weight : 뷰의 가중치로 비율을 통해 너비 혹은 길이를 정함

RalativeLayout

  • View들의 상대적 배치를 제공하는 Layout
  • 특정 뷰를 기준으로 다른 뷰를 배치하는 특징을 가지고 있다.
  • 기준을 정해주지 않게 되면 뷰가 왼쪽 상단에 쌓이게 된다.
  • 뷰 위에 뷰를 겹칠 수 있따.
  • 원하는 배치를 하기 위해서 직접 기준을 정해주면 된다.
  • 복잡한 뷰를 작성할 때 용이하게 사용할 수 있다.

Github : https://github.com/WooVictory/SOPT_22th_Seminar1_HomeWork

Comment and share

  • page 1 of 1
Author's picture

VictoryWoo

기록을 통해 사람들과 공유하는 것을 좋아합니다.


Android Developer