
Snapshot : 데이터를 가지고 있음
Reference : 데이터에 접근할 수 있음
// AddContentFragment.kt
// 글 작성처리 메서드
fun uploadContentData(){
CoroutineScope(Dispatchers.Main).launch {
// 서버에서의 첨부 이미지 파일 이름
var serverFileName:String? = null
// 첨부된 이미지가 있다면
if(isAddPicture == true) {
// 이미지의 뷰의 이미지 데이터를 파일로 저장한다.
Tools.saveImageViewData(contentActivity, fragmentAddContentBinding.imageViewAddContent, "uploadTemp.jpg")
// 서버에서의 파일 이름
serverFileName = "image_${System.currentTimeMillis()}.jpg"
// 서버로 업로드한다.
ContentDao.uploadImage(contentActivity, "uploadTemp.jpg", serverFileName)
}
// 게시글 시퀀스 값을 가져온다.
val contentSequence = ContentDao.getContentSequence() + 1
// 게시글 시퀀스 값을 업데이트 한다.
ContentDao.updateContentSequence(contentSequence)
// 업로드할 정보를 담아준다.
val contentIdx = contentSequence
val contentSubject = addContentViewModel.textFieldAddContentSubject.value!!
val contentType = addContentViewModel.gettingContentType().number
val contentText = addContentViewModel.textFieldAddContentText.value!!
val contentImage = serverFileName
val contentWriterIdx = contentActivity.loginUserIdx
val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
val contentWriteDate = simpleDateFormat.format(Date())
val contentState = ContentState.CONTENT_STATE_NORMAL.number
val contentModel = ContentModel(contentIdx, contentSubject, contentType, contentText, contentImage, contentWriterIdx, contentWriteDate, contentState)
// 업로드한다.
ContentDao.insertContentData(contentModel)
// 키보드를 내려준다.
Tools.hideSoftInput(contentActivity)
// 글 번호를 번들에 담는다.
val readBundle = Bundle()
readBundle.putInt("contentIdx", contentIdx)
// ReadContentFragment로 이동한다.
contentActivity.replaceFragment(ContentFragmentName.READ_CONTENT_FRAGMENT, true, true, readBundle)
}
}
// ContentDao.kt
// 글 번호를 이용해 글 데이터를 가져와 반환한다.
suspend fun selectContentData(contentIdx:Int):ContentModel?{
// 글 데이터 객체를 담을 변수
var contentModel:ContentModel? = null
val job1 = CoroutineScope(Dispatchers.IO).launch {
// 컬렉션에 접근할 수 있는 객체를 가져온다.
val collectionReference = Firebase.firestore.collection("ContentData")
// 컬렉션이 가지고 있는 문서들 중에 contentIdx 필드가 지정된 글 번호값하고 같은 Document들을 가져온다.
val querySnapshot = collectionReference.whereEqualTo("contentIdx", contentIdx).get().await()
// 가져온 글 정보를 객체에 담아서 반환받는다.
// contentIdx가 같은 글은 존재할 수가 없기 때문에 첫 번째 객체를 바로 추출해서 사용한다.
// toObject : 지정한 클래스를 가지고 객체를 만든 다음 가져온 데이터의 필드의 이름과 동일한 이름의 프로퍼티에 필드의 값을 담아준다.
contentModel = querySnapshot.documents[0].toObject(ContentModel::class.java)
}
job1.join()
return contentModel
}
// ReadContentFragment.kt
// 현재 글 번호를 담을 변수
var contentIdx = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// fragmentReadContentBinding = FragmentReadContentBinding.inflate(inflater)
fragmentReadContentBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_read_content, container, false)
readContentViewModel = ReadContentViewModel()
fragmentReadContentBinding.readContentViewModel = readContentViewModel
fragmentReadContentBinding.lifecycleOwner = this
contentActivity = activity as ContentActivity
// 글 번호를 담는다.
contentIdx = arguments?.getInt("contentIdx")!!
settingToolbar()
settingBackButton()
settingInputForm()
return fragmentReadContentBinding.root
}
// 입력 요소에 값을 넣어준다.
fun settingInputForm(){
CoroutineScope(Dispatchers.Main).launch {
// 현재 글 번호에 해당하는 글 데이터를 가져온다.
val contentModel = ContentDao.selectContentData(contentIdx)
// 가져온 데이터를 보여준다.
readContentViewModel.textFieldReadContentSubject.value = contentModel?.contentSubject
readContentViewModel.textFieldReadContentType.value = when(contentModel?.contentType){
ContentType.TYPE_FREE.number -> ContentType.TYPE_FREE.str
ContentType.TYPE_HUMOR.number -> ContentType.TYPE_HUMOR.str
ContentType.TYPE_SOCIETY.number -> ContentType.TYPE_SOCIETY.str
ContentType.TYPE_SPORTS.number -> ContentType.TYPE_SPORTS.str
else -> ""
}
readContentViewModel.textFieldReadContentNickName.value = "홍길동"
readContentViewModel.textFieldReadContentDate.value = contentModel?.contentWriteDate
readContentViewModel.textFieldReadContentText.value = contentModel?.contentText
}
}
idx를 이용해 사용자 정보를 가져오는 메서드가 아직없으므로 메서드를 작성한다.
// UserDao.kt
// 사용자 번호를 통해 사용자 정보를 가져와 반환한다.
suspend fun gettingUserInfoByUserIdx(userIdx:Int):UserModel?{
var userModel:UserModel? = null
val job1 = CoroutineScope(Dispatchers.IO).launch {
// UserData 컬렉션 접근 객체를 가져온다.
val collectionReference = Firebase.firestore.collection("UserData")
// userIdx 필드가 매개변수로 들어오는 userIdx와 같은 문서들을 가져온다.
val querySnapshot = collectionReference.whereEqualTo("userId", userIdx).get().await()
// 가져온 문서 객체들이 들어있는 리스트에서 첫 번째 객체를 추출한다.
// 아이디가 동일한 사용자는 없기 때문에 무조건 하나만 나오기 때문
userModel = querySnapshot.documents[0].toObject(UserModel::class.java)
}
job1.join()
return userModel
}
사용자 닉네임을 불러올 수 있도록 수정해준다.
// ReadContentFragment.kt
// 입력 요소에 값을 넣어준다.
fun settingInputForm(){
CoroutineScope(Dispatchers.Main).launch {
// 현재 글 번호에 해당하는 글 데이터를 가져온다.
val contentModel = ContentDao.selectContentData(contentIdx)
// 글을 작성한 사용자의 번호를 통해 사용자 정보를 가져온다.
val userModel = UserDao.gettingUserInfoByUserIdx(contentModel?.contentWriterIdx!!)
// 가져온 데이터를 보여준다.
readContentViewModel.textFieldReadContentSubject.value = contentModel.contentSubject
readContentViewModel.textFieldReadContentType.value = when(contentModel.contentType){
ContentType.TYPE_FREE.number -> ContentType.TYPE_FREE.str
ContentType.TYPE_HUMOR.number -> ContentType.TYPE_HUMOR.str
ContentType.TYPE_SOCIETY.number -> ContentType.TYPE_SOCIETY.str
ContentType.TYPE_SPORTS.number -> ContentType.TYPE_SPORTS.str
else -> ""
}
readContentViewModel.textFieldReadContentNickName.value = userModel?.userNickName
readContentViewModel.textFieldReadContentDate.value = contentModel.contentWriteDate
readContentViewModel.textFieldReadContentText.value = contentModel.contentText
}
}
이미지도 불러와야 하므로 서버에 저장한 이미지를 불러오는 메서드를 작성해야 한다.
Glide를 이용하면 서버로부터 해당 이미지를 찾아 Uri객체를 전달받아 이미지뷰에 넣어주는 작업을 해준다.
https://github.com/bumptech/glide
build.gradle.kts(:app)에 추가
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation(platform("com.google.firebase:firebase-bom:32.7.4"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-firestore:24.10.3")
implementation("com.google.firebase:firebase-storage:20.3.0")
implementation("com.github.bumptech.glide:glide:4.16.0")
}
이미지 데이터를 받아와 이미지뷰에 보여주는 메서드 작성
이미지뷰에 보여주는 작업은 Dispatchers.Main으로 코루틴을 열어준다
// ContentDao.kt
// 이미지 데이터를 받아오는 메서드
suspend fun gettingContentImage(context:Context, imageFileName:String, imageView:ImageView){
CoroutineScope(Dispatchers.IO).launch {
// 이미지에 접근할 수 있는 객체를 가져온다.
val storageRef = Firebase.storage.reference.child("iamge/$imageFileName")
// 이미지의 주소를 가지고 있는 Uri 객체를 받아온다.
val imageUri = storageRef.downloadUrl.await()
// 이미지 데이터를 받아와 이미지 뷰에 보여준다.
// 화면 관련된 작업이므로 Dispatchers.Main으로 코루틴을 한번 더 열어준다.
CoroutineScope(Dispatchers.Main).launch{
Glide.with(context).load(imageUri).into(imageView)
// 이미지뷰가 나타나게 한다.
imageView.visibility = View.VISIBLE
}
}
// 이미지는 용량이 매우 클 수 있다. 즉 이미지 데이터를 내려받는데 시간이 오래걸리 수도 있다.
// 이에, 이미지 데이터를 받아와 보여주는 코루틴을 작업이 끝날 때 까지 대기하지 않는다.
// 그 이유는 데이터를 받아오는데 걸리는 오랜 시간 동안 화면에 아무것도 나타나지 않을 수 있기 때문이다.
// 따라서 이 메서드는 제일 마지막에 호출해야 한다.(다른 것들을 모두 보여준 후에...)
}
글 데이터를 불러오는 과정에서 이미지뷰를 먼저 안보이게 한 뒤 이미지를 가져오면 이미지뷰를 보이게 메서드를 수정한다.
// ReadContentFragment.kt
// 입력 요소에 값을 넣어준다.
fun settingInputForm(){
CoroutineScope(Dispatchers.Main).launch {
// 이미지뷰를 안보이게 한다.
fragmentReadContentBinding.imageViewReadContent.visibility = View.INVISIBLE
// 현재 글 번호에 해당하는 글 데이터를 가져온다.
val contentModel = ContentDao.selectContentData(contentIdx)
// 글을 작성한 사용자의 번호를 통해 사용자 정보를 가져온다.
val userModel = UserDao.gettingUserInfoByUserIdx(contentModel?.contentWriterIdx!!)
// 가져온 데이터를 보여준다.
readContentViewModel.textFieldReadContentSubject.value = contentModel.contentSubject
readContentViewModel.textFieldReadContentType.value = when(contentModel.contentType){
ContentType.TYPE_FREE.number -> ContentType.TYPE_FREE.str
ContentType.TYPE_HUMOR.number -> ContentType.TYPE_HUMOR.str
ContentType.TYPE_SOCIETY.number -> ContentType.TYPE_SOCIETY.str
ContentType.TYPE_SPORTS.number -> ContentType.TYPE_SPORTS.str
else -> ""
}
readContentViewModel.textFieldReadContentNickName.value = userModel?.userNickName
readContentViewModel.textFieldReadContentDate.value = contentModel.contentWriteDate
readContentViewModel.textFieldReadContentText.value = contentModel.contentText
// 이미지 데이터를 불러온다.
if(contentModel.contentImage != null){
ContentDao.gettingContentImage(contentActivity, contentModel.contentImage!!, fragmentReadContentBinding.imageViewReadContent)
}
}
}

요즘은 로딩바 표시 대신 스켈레톤 로딩 화면을 사용한다.
데이터를 불러올 때 로딩중인 화면을 먼저 표시하고 데이터 불러오기가 완료되면
원래 화면을 표시한다.
따라서 로딩중인 화면을 별도로 직접 만들어주거나 라이브러리(Shimmer 등)를 사용하면 된다.