[Android/Flutter 교육] 55일차

MSU·2024년 3월 20일

Android-Flutter

목록 보기
58/85
post-thumbnail

게시판 프로젝트

게시판 별 글 목록 불러오기

게시판 메뉴를 클릭할 때 번들에 게시판 제목을 담아 MainFragment로 이동

// ContentAcitivity.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityContentBinding = ActivityContentBinding.inflate(layoutInflater)
        setContentView(activityContentBinding.root)

        // 로그인한 사용자 정보를 변수에 담아둔다.
        loginUserIdx = intent.getIntExtra("loginUserIdx", 0)
        loginUserNickName = intent.getStringExtra("loginUserNickName")!!

        settingNavigationView()

        val mainBundle = Bundle()
        mainBundle.putString("TypeName",ContentType.TYPE_ALL.str)
        mainBundle.putInt("TypeNumber",ContentType.TYPE_ALL.number)
        replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
    }



    // 네비게이션 뷰 설정
    fun settingNavigationView(){
        activityContentBinding.apply {
            navigationViewContent.apply {
                // 헤더로 보여줄 view를 생성한다.
                val headerContentDrawerBinding = HeaderContentDrawerBinding.inflate(layoutInflater)
                // 헤더로 보여줄 View를 설정한다.
                addHeaderView(headerContentDrawerBinding.root)
                
                // 사용자 닉네임을 설정한다.
                headerContentDrawerBinding.headerContentDrawerNickName.text = loginUserNickName

                // 메뉴를 눌렀을 때 동작하는 리스너
                setNavigationItemSelectedListener {
                    // 딜레이를 조금 준다.
                    SystemClock.sleep(200)
                    // 메뉴의 id로 분기한다.
                    when(it.itemId){
                        // 전체 게시판
                        R.id.menuItemContentNavigationAll -> {
                            val mainBundle = Bundle()
                            // 게시판 종류를 담는다.
                            mainBundle.putString("TypeName",ContentType.TYPE_ALL.str)
                            mainBundle.putInt("TypeNumber",ContentType.TYPE_ALL.number)
                            replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        // 자유 게시판
                        R.id.menuItemContentNavigation1 -> {
                            val mainBundle = Bundle()
                            // 게시판 종류를 담는다.
                            mainBundle.putString("TypeName",ContentType.TYPE_FREE.str)
                            mainBundle.putInt("TypeNumber",ContentType.TYPE_FREE.number)
                            replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        // 유머 게시판
                        R.id.menuItemContentNavigation2 -> {
                            val mainBundle = Bundle()
                            // 게시판 종류를 담는다.
                            mainBundle.putString("TypeName",ContentType.TYPE_HUMOR.str)
                            mainBundle.putInt("TypeNumber",ContentType.TYPE_HUMOR.number)
                            replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        // 시사 게시판
                        R.id.menuItemContentNavigation3 -> {
                            val mainBundle = Bundle()
                            // 게시판 종류를 담는다.
                            mainBundle.putString("TypeName",ContentType.TYPE_SOCIETY.str)
                            mainBundle.putInt("TypeNumber",ContentType.TYPE_SOCIETY.number)
                            replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        // 스포츠 게시판
                        R.id.menuItemContentNavigation4 -> {
                            val mainBundle = Bundle()
                            // 게시판 종류를 담는다.
                            mainBundle.putString("TypeName",ContentType.TYPE_SPORTS.str)
                            mainBundle.putInt("TypeNumber",ContentType.TYPE_SPORTS.number)
                            replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, mainBundle)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        
                        // 사용자 정보 수정
                        R.id.menuItemContentNavigationModifyUserInfo -> {
                            replaceFragment(ContentFragmentName.MODIFY_USER_FRAGMENT, false, false, null)
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()
                        }
                        // 로그아웃
                        R.id.menuItemContentNavigationLogout -> {
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()

                            // MainActivity를 실행한다.
                            val mainIntent = Intent(this@ContentActivity, MainActivity::class.java)
                            startActivity(mainIntent)
                            // ContentActivity를 종료한다.
                            this@ContentActivity.finish()
                        }
                        // 회원탈퇴
                        R.id.menuItemContentNavigationSignOut -> {
                            // NavigationView를 닫아준다.
                            drawerLayoutContent.close()

                            // MainActivity를 실행한다.
                            val mainIntent = Intent(this@ContentActivity, MainActivity::class.java)
                            startActivity(mainIntent)
                            // ContentActivity를 종료한다.
                            this@ContentActivity.finish()
                        }
                    }
                    true
                }
            }
        }
    }

게시판 종류값을 담을 프로퍼티 셋팅

// MainFragment.kt


    // 게시판 종류를 담을 프로퍼티
    var contentType = 0

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        // 게시판 종류값을 담아준다.
        contentType = arguments?.getInt("TypeNumber")!!

        settingToolbar()
        settingRecyclerViewMain()
        settingRecyclerViewMainSearch()
        settingSearchBar()

        return fragmentMainBinding.root
    }

번들로 전달받은 값을 툴바 타이틀에 셋팅

// MainFragment.kt


    // 툴바 설정
    fun settingToolbar(){
        fragmentMainBinding.apply {
            toolbarMain.apply {
                // 타이틀
                title = arguments?.getString("TypeName")

                // 메뉴
                inflateMenu(R.menu.menu_content_main)
                setNavigationIcon(R.drawable.menu_24px)
                setNavigationOnClickListener {
                    contentActivity.activityContentBinding.drawerLayoutContent.open()
                }
            }
        }
    }

게시글 목록을 가져오는 메서드 작성

// ContentDao.kt



        // 게시글 목록을 가져온다.
        suspend fun gettingContentList(contentType:Int):MutableList<ContentModel>{
            // 게시글 정보를 담을 리스트
            val contentList = mutableListOf<ContentModel>()

            val job1 = CoroutineScope(Dispatchers.IO).launch {
                // 컬렉션에 접근할 수 있는 객체를 가져온다.
                val collectionReference = Firebase.firestore.collection("ContentData")
                // 게시글 상태가 정상
                var query = collectionReference.whereEqualTo("contentState", ContentState.CONTENT_STATE_NORMAL.number)
                // 게시글 번호를 기준으로 내림차순 정렬
                query = query.orderBy("contentIdx",Query.Direction.DESCENDING)
                // 만약 전체 게시판이 아니라면
                if(contentType != ContentType.TYPE_ALL.number){
                    query = query.whereEqualTo("contentType", contentType)
                }
                val querySnapshot = query.get().await()
                querySnapshot.forEach {
                    // 현재 번째의 문서를 객체로 받아온다.
                    val contentModel = it.toObject(ContentModel::class.java)
                    // 객체를 리스트에 담는다.
                    contentList.add(contentModel)
                }
            }
            job1.join()

            return contentList
        }

쿼리 작성시 order절 적용할 때 주의점

아래와같이 where조건을 작성하고 order조건을 작성한 후에 다시 where조건을 작성한다면


            val job1 = CoroutineScope(Dispatchers.IO).launch {
                // 컬렉션에 접근할 수 있는 객체를 가져온다.
                val collectionReference = Firebase.firestore.collection("ContentData")
                // 게시글 상태가 정상
                var query = collectionReference.whereEqualTo("contentState", ContentState.CONTENT_STATE_NORMAL.number)
                // 게시글 번호를 기준으로 내림차순 정렬
                query = query.orderBy("contentIdx",Query.Direction.DESCENDING)
                // 만약 전체 게시판이 아니라면
                if(contentType != ContentType.TYPE_ALL.number){
                    query = query.whereEqualTo("contentType", contentType)
                }
                val querySnapshot = query.get().await()
                querySnapshot.forEach {
                    contentList.add(it.toObject(ContentModel::class.java))
                }
            }


이러한 에러가 발생하게 되는데

Caused by: io.grpc.StatusException: FAILED_PRECONDITION: The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/ ...

직접 파이어베이스로 들어가서 인덱스를 만들라는 링크가 에러 메시지에 같이 첨부가 된다.

파이어 스토어에는 미리 데이터를 정리해주는 인덱싱 과정이 있어서 이를 통해 원하는 데이터를 빠르게 가져올 수 있게 하는데 정렬을 위해 인덱스가 필요하다보니 인덱스를 만들어줘야하는 과정을 직접 처리해주면 된다.

인덱스가 생성되고 코드를 다시 실행하면 에러 없이 정상 실행되는 것을 확인할 수 있다.

받아온 게시글 목록을 받아와 메인화면의 리사이클러뷰를 갱신하는 메서드

// MainFragment.kt


    // 메인 화면의 RecyclerView 구성을 위한 리스트
    var mainList = mutableListOf<ContentModel>()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        // 게시판 종류값을 담아준다.
        contentType = arguments?.getInt("TypeNumber")!!

        settingToolbar()
        settingRecyclerViewMain()
        settingRecyclerViewMainSearch()
        settingSearchBar()
        gettingMainData()

        return fragmentMainBinding.root
    }

 
// 현재 게시판의 데이터를 가져와 메인 화면의 리사이클러뷰를 갱신한다.
    fun gettingMainData(){
        CoroutineScope(Dispatchers.Main).launch {
            // 서버에서 데이터를 가져온다.
            mainList = ContentDao.gettingContentList(contentType)
            // 리사이클러뷰를 갱신한다.
            fragmentMainBinding.recyclerViewMain.adapter?.notifyDataSetChanged()
        }
    }

모든 사용자 정보를 가져오는 메서드 작성

가져온 글의 작성자 닉네임을 파악하기 위해 모든 사용자 정보를 가져오는 메서드를 작성한다.

// UserDao.kt



        // 모든 사용자의 정보를 가져온다.
        suspend fun getUserAll():MutableList<UserModel>{
            // 사용자 정보를 담을 리스트
            val userList = mutableListOf<UserModel>()

            val job1 = CoroutineScope(Dispatchers.IO).launch {
                // 모든 사용자 정보를 가져온다.
                val querySnapshot = Firebase.firestore.collection("UserData").get().await()
                // 가져온 문서의 수 만큼 반복한다.
                querySnapshot.forEach {
                    // UserModel 객체에 담는다.
                    val userModel = it.toObject(UserModel::class.java)
                    // 리스트에 담는다.
                    userList.add(userModel)
                }
            }
            job1.join()

            return userList
        }
        

리사이클러뷰 갱신 메서드에 사용자 정보도 같이 가져온다.

// MainFragment.kt

    // 사용자 정보를 담고 있을 리스트
    var userList = mutableListOf<UserModel>()


    // 현재 게시판의 데이터를 가져와 메인 화면의 리사이클러뷰를 갱신한다.
    fun gettingMainData(){
        CoroutineScope(Dispatchers.Main).launch {
            // 서버에서 데이터를 가져온다.
            mainList = ContentDao.gettingContentList(contentType)
            // 사용자 정보를 가져온다.
            userList = UserDao.getUserAll()
            // 리사이클러뷰를 갱신한다.
            fragmentMainBinding.recyclerViewMain.adapter?.notifyDataSetChanged()
        }
    }

리사이클러뷰의 onBindViewHolder 수정

사용자 번호와 작성자 번호가 같은 경우에 닉네임을 출력하도록 한다.

// MainFragment.kt


        override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
            holder.rowMainBinding.textViewRowMainSubject.text = mainList[position].contentSubject

            // 사용자의 수 만큼 반복한다.
            userList.forEach {
                // 사용자 번호와 작성자 번호가 같으면 출력하고 중단한다.
                if(it.userIdx == mainList[position].contentWriterIdx){
                    holder.rowMainBinding.textViewRowMainNickName.text = it.userNickName
                    return@forEach
                }
            }
        }

파이어베이스의 스토리지는 noSql기반이라 join을 사용할 수 없어서 이런 방식으로 작업할 수 밖에 없다고 한다.

게시글모델에 사용자 닉네임 필드를 추가하는 방법도 고려하면 좋을 것 같다.

리사이클러뷰 항목을 눌렀을 때 동작할 리스너 연결

// MainFragment.kt



        override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
            holder.rowMainBinding.textViewRowMainSubject.text = mainList[position].contentSubject

            // 사용자의 수 만큼 반복한다.
            userList.forEach {
                // 사용자 번호와 작성자 번호가 같으면 출력하고 중단한다.
                if(it.userIdx == mainList[position].contentWriterIdx){
                    holder.rowMainBinding.textViewRowMainNickName.text = it.userNickName
                    return@forEach
                }
            }

            // 항목을 눌렀을 때 동작할 리스너를 연결해준다.
            holder.rowMainBinding.root.setOnClickListener {
                // 필요한 데이터를 담아준다.
                val readBundle = Bundle()
                readBundle.putInt("contentIdx", mainList[position].contentIdx)
                // 글 읽는 화면으로 이동한다.
                contentActivity.replaceFragment(ContentFragmentName.READ_CONTENT_FRAGMENT, true, true, readBundle)
            }
        }

profile
안드로이드공부

0개의 댓글