1

作为一名需要适应变化的开发人员,我在某处读到它说:

如果您没有为您的 Android 项目选择正确的架构,那么随着代码库的增长和团队的扩展,您将很难维护它。

我想Clean ArchitectureMVVM

我的应用数据流将如下所示:

OneNote_Clean_Architecture_MVVM_Data_Flow

模型类

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工作正常。

4

0 回答 0