作为一名需要适应变化的开发人员,我在某处读到它说:
如果您没有为您的 Android 项目选择正确的架构,那么随着代码库的增长和团队的扩展,您将很难维护它。
我想Clean Architecture
用MVVM
我的应用数据流将如下所示:
模型类
data class Note(
val title: String? = null,
val timestamp: String? = null
)
Dtos
data class NoteRequest(
val title: String? = null,
val timestamp: String? = null
)
和
data class NoteResponse(
val id: String? = null,
val title: String? = null,
val timestamp: String? = null
)
存储层是
interface INoteRepository {
fun getNoteListSuccessListener(success: (List<NoteResponse>) -> Unit)
fun deleteNoteSuccessListener(success: (List<NoteResponse>) -> Unit)
fun getNoteList()
fun deleteNoteById(noteId: String)
}
NoteRepositoryImpl 是:
class NoteRepositoryImpl: INoteRepository {
private val mFirebaseFirestore = Firebase.firestore
private val mNotesCollectionReference = mFirebaseFirestore.collection(COLLECTION_NOTES)
private val noteList = mutableListOf<NoteResponse>()
private var getNoteListSuccessListener: ((List<NoteResponse>) -> Unit)? = null
private var deleteNoteSuccessListener: ((List<NoteResponse>) -> Unit)? = null
override fun getNoteListSuccessListener(success: (List<NoteResponse>) -> Unit) {
getNoteListSuccessListener = success
}
override fun deleteNoteSuccessListener(success: (List<NoteResponse>) -> Unit) {
deleteNoteSuccessListener = success
}
override fun getNoteList() {
mNotesCollectionReference
.addSnapshotListener { value, _ ->
noteList.clear()
if (value != null) {
for (item in value) {
noteList
.add(item.toNoteResponse())
}
getNoteListSuccessListener?.invoke(noteList)
}
Log.e("NOTE_REPO", "$noteList")
}
}
override fun deleteNoteById(noteId: String) {
mNotesCollectionReference.document(noteId)
.delete()
.addOnSuccessListener {
deleteNoteSuccessListener?.invoke(noteList)
}
}
}
ViewModel 层是:
interface INoteViewModel {
val noteListStateFlow: StateFlow<List<NoteResponse>>
val noteDeletedStateFlow: StateFlow<List<NoteResponse>>
fun getNoteList()
fun deleteNoteById(noteId: String)
}
NoteViewModelImpl 是:
class NoteViewModelImpl: ViewModel(), INoteViewModel {
private val mNoteRepository: INoteRepository = NoteRepositoryImpl()
private val _noteListStateFlow = MutableStateFlow<List<NoteResponse>>(mutableListOf())
override val noteListStateFlow: StateFlow<List<NoteResponse>>
get() = _noteListStateFlow.asStateFlow()
private val _noteDeletedStateFlow = MutableStateFlow<List<NoteResponse>>(mutableListOf())
override val noteDeletedStateFlow: StateFlow<List<NoteResponse>>
get() = _noteDeletedStateFlow.asStateFlow()
init {
// getNoteListSuccessListener
mNoteRepository
.getNoteListSuccessListener {
viewModelScope
.launch {
_noteListStateFlow.emit(it)
Log.e("NOTE_G_VM", "$it")
}
}
// deleteNoteSuccessListener
mNoteRepository
.deleteNoteSuccessListener {
viewModelScope
.launch {
_noteDeletedStateFlow.emit(it)
Log.e("NOTE_D_VM", "$it")
}
}
}
override fun getNoteList() {
// Get all notes
mNoteRepository.getNoteList()
}
override fun deleteNoteById(noteId: String) {
mNoteRepository.deleteNoteById(noteId = noteId)
}
}
最后但并非最不重要的片段是:
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private val viewModel: INoteViewModel by viewModels<NoteViewModelImpl>()
private lateinit var adapter: NoteAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = binding.recyclerViewNotes
recyclerView.addOnScrollListener(
ExFABScrollListener(binding.fab)
)
adapter = NoteAdapter{itemView, noteId ->
if (noteId != null) {
showMenu(itemView, noteId)
}
}
recyclerView.adapter = adapter
// initView()
fetchFirestoreData()
binding.fab.setOnClickListener {
val action = HomeFragmentDirections.actionFirstFragmentToSecondFragment()
findNavController().navigate(action)
}
}
private fun fetchFirestoreData() {
// Get note list
viewModel
.getNoteList()
// Create list object
val noteList:MutableList<NoteResponse> = mutableListOf()
// Impose StateFlow
viewModel
.noteListStateFlow
.onEach { data ->
data.forEach {noteResponse ->
noteList.add(noteResponse)
adapter.submitList(noteList)
Log.e("NOTE_H_FRAG", "$noteResponse")
}
}.launchIn(viewLifecycleOwner.lifecycleScope)
}
//In the showMenu function from the previous example:
@SuppressLint("RestrictedApi")
private fun showMenu(v: View, noteId: String) {
val menuBuilder = MenuBuilder(requireContext())
SupportMenuInflater(requireContext()).inflate(R.menu.menu_note_options, menuBuilder)
menuBuilder.setCallback(object : MenuBuilder.Callback {
override fun onMenuItemSelected(menu: MenuBuilder, item: MenuItem): Boolean {
return when(item.itemId){
R.id.option_edit -> {
val action = HomeFragmentDirections.actionFirstFragmentToSecondFragment(noteId = noteId)
findNavController().navigate(action)
true
}
R.id.option_delete -> {
viewModel
.deleteNoteById(noteId = noteId)
// Create list object
val noteList:MutableList<NoteResponse> = mutableListOf()
viewModel
.noteDeletedStateFlow
.onEach {data ->
data.forEach {noteResponse ->
noteList.add(noteResponse)
adapter.submitList(noteList)
Log.e("NOTE_H_FRAG", "$noteResponse")
}
}.launchIn(viewLifecycleOwner.lifecycleScope)
true
} else -> false
}
}
override fun onMenuModeChange(menu: MenuBuilder) {}
})
val menuHelper = MenuPopupHelper(requireContext(), menuBuilder, v)
menuHelper.setForceShowIcon(true) // show icons!!!!!!!!
menuHelper.show()
}
}
有了上述所有逻辑,我面临着TWO
问题
问题 - 1 如前所述,我在集合中添加了 SnapshotListener:
override fun getNoteList() {
mNotesCollectionReference
.addSnapshotListener { value, _ ->
noteList.clear()
if (value != null) {
for (item in value) {
noteList
.add(item.toNoteResponse())
}
getNoteListSuccessListener?.invoke(noteList)
}
Log.e("NOTE_REPO", "$noteList")
}
}
如果我从Firebase 控制台Repository
更改文档的值,我会在和中获得更新的值ViewModel
,但不会更新传递给的注释列表adapter
,因此所有项目都是相同的。
问题 - 2
如果我使用以下命令从列表/回收站视图中删除任何项目:
R.id.option_delete -> {
viewModel
.deleteNoteById(noteId = noteId)
// Create list object
val noteList:MutableList<NoteResponse> = mutableListOf()
viewModel
.noteDeletedStateFlow
.onEach {data ->
data.forEach {noteResponse ->
noteList.add(noteResponse)
adapter.submitList(noteList)
Log.e("NOTE_H_FRAG", "$noteResponse")
}
}.launchIn(viewLifecycleOwner.lifecycleScope)
Repository
我仍然在和中获得更新的列表(即新的笔记列表,不包括已删除的笔记)ViewModel
,但没有更新传递给的笔记列表adapter
,因此所有项目都是相同的,没有并排除已删除的项目。
问题我在初始化/更新适配器时到底在哪里出错?因为ViewModel
并且Repository
工作正常。