210202

Kotlin 데이터 바인딩과 네비게이션을 사용하여 프래그먼트 구현 + 몇가지 오류들 해결 본문

Android

Kotlin 데이터 바인딩과 네비게이션을 사용하여 프래그먼트 구현 + 몇가지 오류들 해결

dev210202 2020. 3. 9. 18:42

MVVM 패턴으로 앱을 만들기 위해서 데이터 바인딩을 사용해보았다. 화면전환은 네비게이션을 사용해서 구현해보았다.

데이터 바인딩을 하는 과정은 다음 사이트를 참고해서 구현했다.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-data-binding-basics/index.html?index=..%2F..android-kotlin-fundamentals#0

프래그먼트 구현은 다음 사이트를 참고해서 구현했다.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-create-and-add-fragment/index.html?index=..%2F..android-kotlin-fundamentals#0

네비게이션 구현은 다음 사이트를 참고해서 구현했다.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-add-navigation/index.html?index=..%2F..android-kotlin-fundamentals#0

예제 사이트를 참고해서 구현하면 어렵지 않게 구현할 수 있는데 내가 겪은 오류들만 정리해보겠다.

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>
Comments