210202
Kotlin 데이터 바인딩과 네비게이션을 사용하여 프래그먼트 구현 + 몇가지 오류들 해결 본문
MVVM 패턴으로 앱을 만들기 위해서 데이터 바인딩을 사용해보았다. 화면전환은 네비게이션을 사용해서 구현해보았다.
데이터 바인딩을 하는 과정은 다음 사이트를 참고해서 구현했다.
프래그먼트 구현은 다음 사이트를 참고해서 구현했다.
네비게이션 구현은 다음 사이트를 참고해서 구현했다.
예제 사이트를 참고해서 구현하면 어렵지 않게 구현할 수 있는데 내가 겪은 오류들만 정리해보겠다.
1. duplicate attribute 오류
duplicate attribute~ 라고 오류가 나오면서 앱을 실행할 수 없는 경우가 있다. 이 경우는 fragment와 데이터 바인딩을 같이 하다가 xml파일에서 layout 태그를 설정하고 view의 속성들을 제거하지 않아서 생겼다. 꼭 다음과 같이 설정하자
<layout 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"
>
2. Fragment~Binding 오류
예제에서 val binding = DataBindingUtil.inflate<FragmentTitleBinding>( ~ 을 따라했는데 FragmentTitleBinding에서 오류가 나는 경우가 있다. 이 경우에는 제대로 build.gradle에 요구사항들을 추가하지 않았거나 xml 파일에서 문제가 있는 경우다.
3. view.findNavController.navigate 오류
네비게이션을 사용하면 view.findNavController.navigate 함수를 사용해서 다음 fragment로 넘어가게 되는데 지금 하고있는 프로젝트에서는 버튼이 눌리고 나서 정보를 가져온 뒤 이동해야하기 때문에 onClickListener에서 실행할 수 없어서 view를 가져오지 못한다. 해결방법은 findNavController.navigate로 함수를 사용하면 정상적으로 넘어가게 된다.
구현코드
MainActivity
package org.first.mnkotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
LoginFragment
package org.first.mnkotlin
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.navigation.fragment.findNavController
import com.facebook.*
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import org.first.mnkotlin.databinding.FragmentLoginBinding
import org.json.JSONObject
import java.util.*
/**
* A simple [Fragment] subclass.
*/
class LoginFragment : Fragment() {
private lateinit var accessToken: AccessToken
private lateinit var result: FacebookCallback<LoginResult>
private var isLoggedInWithFacebook: Boolean = false
private lateinit var userEmail: String
private lateinit var userName: String
private lateinit var jobj1: JSONObject
private lateinit var jobj2: JSONObject
private lateinit var userPicture: String
private var callbackManager: CallbackManager? = null
private lateinit var binding: FragmentLoginBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<FragmentLoginBinding>(
inflater,
R.layout.fragment_login,
container,
false
)
binding.facebookloginbutton.setOnClickListener { view: View ->
getCallBack()
}
return binding.root
}
fun getCallBack() {
callbackManager = CallbackManager.Factory.create()
LoginManager.getInstance()
.logInWithReadPermissions(this, Arrays.asList("public_profile", "email"))
LoginManager.getInstance().registerCallback(callbackManager,
object : FacebookCallback<LoginResult> {
override fun onSuccess(loginResult: LoginResult) {
accessToken = loginResult.accessToken
isLoggedInWithFacebook = accessToken != null && !accessToken.isExpired()
requestMe(accessToken)
}
override fun onCancel() {
}
override fun onError(error: FacebookException?) {
}
})
}
fun requestMe(accessToken: AccessToken) {
val request = GraphRequest.newMeRequest(accessToken) { `object`, response ->
try {
//here is the data that you want
userEmail = `object`.getString("email")
Log.e("TAGG", userEmail)
userName = `object`.getString("name")
Log.e("TAGG", userName)
jobj1 = `object`.optJSONObject("picture")
Log.e("TAGG", jobj1.toString())
jobj2 = jobj1.optJSONObject("data")
Log.e("TAGG", jobj2.toString())
userPicture = jobj2.getString("url")
Log.e("TAGG", userPicture)
} catch (e: Exception) {
e.printStackTrace()
}
findNavController().navigate(R.id.action_loginFragment2_to_selectPictureFragment)
}
val parameters = Bundle()
parameters.putString("fields", "name,email,picture")
request.parameters = parameters
request.executeAsync()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
callbackManager?.onActivityResult(requestCode, resultCode, data)
}
override fun onStart() {
super.onStart()
if (AccessToken.getCurrentAccessToken() != null) {
accessToken = AccessToken.getCurrentAccessToken()
isLoggedInWithFacebook = accessToken != null && !accessToken.isExpired()
}
if (isLoggedInWithFacebook) {
requestMe(accessToken)
} else {
}
}
}
SelectPictureFragment
package org.first.mnkotlin
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import org.first.mnkotlin.databinding.FragmentSelectPictureBinding
/**
* A simple [Fragment] subclass.
*/
class SelectPictureFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentSelectPictureBinding>(inflater, R.layout.fragment_select_picture, container, false)
return binding.root
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/loginFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/navigation"
app:defaultNavHost="true"
/>
</LinearLayout>
</layout>
fragment_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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"
tools:context="org.first.mnkotlin.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.facebook.login.widget.LoginButton
android:id="@+id/facebookloginbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.facebook.login.widget.LoginButton>
</LinearLayout>
</layout>
fragment_select_picture.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="123123"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
</layout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/navigation"
app:startDestination="@id/loginFragment2">
<fragment
android:id="@+id/selectPictureFragment"
android:name="org.first.mnkotlin.SelectPictureFragment"
android:label="SelectPictureFragment"
tools:layout="@layout/fragment_select_picture"/>
<fragment
android:id="@+id/loginFragment2"
android:name="org.first.mnkotlin.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment2_to_selectPictureFragment"
app:destination="@id/selectPictureFragment" />
</fragment>
</navigation>
'Android' 카테고리의 다른 글
[Kotlin] 움직이는(GIF) ProgressDialog로 로딩처리 (2) | 2020.05.16 |
---|---|
[Kotlin] RecyclerView를 ListView의 단일선택처럼 사용하기 (0) | 2020.03.20 |
Kotlin으로 Facebook 소셜 로그인 구현 + 유저 데이터 가져오기 (0) | 2020.03.08 |
T Map API 다중마커 구현 + 풍선뷰 이벤트 처리(풍선뷰 누를 시 마커삭제) (0) | 2020.02.24 |
Retrofit을 사용한 서버통신(jsonplaceholder 사이트 사용) (0) | 2020.02.22 |