Я использую навигационную архитектуру Jetpack с navhost, navgraph и библиотекой базы данных Room SQLite. В моем приложении к каждой категории добавлены задачи. В одном из моих фрагментов под названием CategoryFragment
все задачи в определенной категории c перечислены в RecyclerView
, который я получаю, вызывая оператор SQL и передавая ему categoryId.
У меня также есть FAB в этом фрагменте, который при нажатии выскакивает фрагмент диалога под названием NewTaskDialogFragment
. В этом диалоговом окне пользователь вводит название и описание задачи. Нажатие кнопки ОК добавит задачу в базу данных, а затем вернет backstack в go обратно в CategoryFragment
. Но это не перезагружает CategoryFragment
и впоследствии не выполняет вызов SQL для извлечения задач из базы данных и обновления RecyclerView
. Фактически, ни один из методов жизненного цикла не вызывается на CategoryFragment
.
Чтобы исправить это, я попробовал другой подход и использовал SafeArgs
для передачи имени и описания задачи в CategoryFragment
, но это вызывает другую проблему, при которой система снова добавляет CategoryFragment
в backstack. так что мне нужно дважды нажать кнопку возврата, чтобы go вернуться к предыдущему фрагменту в backstack перед CategoryFragment
. Итак, как лучше всего выполнить sh задачу вставки данных из диалогового окна в базу данных и одновременно обновить RecyclerView
? Спасибо.
CategoryFragment
package com.example.pomoplay.ui.main
import android.content.Context
import android.os.Build
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 android.widget.SearchView
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.pomoplay.PomoPlayObservablesSingleton
import com.example.pomoplay.R
import com.example.pomoplay.TasksRecyclerAdapter
import kotlinx.android.synthetic.main.fragment_categories.*
import kotlinx.android.synthetic.main.fragment_category.*
class CategoryFragment : Fragment(), SearchView.OnQueryTextListener {
lateinit var navController: NavController
private var adapter: TasksRecyclerAdapter? = null
private val viewModel: CategoryTasksViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_category, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
observerSetup()
recyclerSetup()
var searchView = category_tasks_searchview
searchView.setOnQueryTextListener(this)
fab_new_task.setOnClickListener {
navController.navigate(R.id.action_categoryFragment_to_newTaskDialogFragment)
}
navController = Navigation.findNavController(view)
showTasks()
}
private fun showTasks() {
if (!arguments?.isEmpty!!) {
var args = CategoryFragmentArgs.fromBundle(arguments!!)
category_title.text = args.category?.name
var category = args.category
viewModel.setPomoCategoryName(category.name)
viewModel.setCategoryId(category.id)
viewModel.searchTasksByCategoryId(category.id)
}
else{
category_title.text = viewModel.getPomoCategoryName()
viewModel.searchTasksByCategoryId(viewModel.getCategoryId())
}
}
private fun observerSetup() {
viewModel.getSearchTasksByCategoryIdResults().observe(this,androidx.lifecycle.Observer { tasks ->
if(tasks.isNotEmpty()){
adapter?.setTasksList(tasks.sortedBy { task -> task.name?.toLowerCase() })
task_not_found_bubble.visibility = View.GONE
task_not_found_text.visibility = View.GONE
}
else{
task_not_found_bubble.visibility = View.VISIBLE
task_not_found_text.visibility = View.VISIBLE
}
})
}
private fun recyclerSetup() {
adapter = context?.let { TasksRecyclerAdapter(it) }
tasks_list?.layoutManager = LinearLayoutManager(context)
tasks_list?.adapter = adapter
}
override fun onQueryTextSubmit(query: String?): Boolean {
Log.i("Lifecycle-CatFragment", "onQueryTextSubmit() called")
var q = query?.toLowerCase()?.trim()?.replace("\\s+".toRegex(), " ")
setLastSearchQuery(q.toString())
viewModel.searchTasksByName(viewModel.getLastSearchQuery().toString())
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
Log.i("Lifecycle-CatFragment", "onQueryTextChange() called")
return false
}
private fun setLastSearchQuery(lastSearchQuery: String) {
viewModel.setLastSearchQuery(lastSearchQuery)
}
}
NewTaskDialogFragment
class NewTaskDialogFragment : DialogFragment() {
private lateinit var taskNameEditText: EditText
private lateinit var taskDescEditText: EditText
private lateinit var navController: NavController
private val viewModel: CategoryTasksViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
super.onCreateDialog(savedInstanceState)
navController = Navigation.findNavController(parentFragment?.view!!)
var catList = ArrayList<String>()
catList.add("Test Item 1")
catList.add("Test Item 2")
val view =
requireActivity().layoutInflater.inflate(R.layout.fragment_new_task_dialog, null)
taskNameEditText = view.findViewById(R.id.dialog_new_task_name) as EditText
taskDescEditText = view.findViewById(R.id.dialog_new_task_desc) as EditText
return activity?.let { it ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it)
builder.setTitle("Testing")
.setPositiveButton(
"ok"
) { _, id ->
var task = PomoTask(taskNameEditText.text.toString().trim().replace("\\s+".toRegex(), " "), taskDescEditText.text.toString().trim().replace("\\s+"," "))
task.categoryId = viewModel.getCategoryId()
viewModel.insertTask(task)
navController.popBackStack()
}
.setNegativeButton("cancel") { _, id ->
}
.setView(view)
var spinner = view.findViewById<Spinner>(R.id.dialog_new_task_spinner)
var spinnerAdapter =
context?.let {
ArrayAdapter<String>(
it,
android.R.layout.simple_spinner_item,
catList
)
}
spinnerAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerAdapter
val dialog = builder.create()
dialog.setOnShowListener { dialog ->
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = taskNameEditText.text.isNotBlank() && taskNameEditText.text.isNotEmpty()
}
taskNameEditText.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(
s: CharSequence, start: Int, before: Int,
count: Int
) {
}
override fun beforeTextChanged(
s: CharSequence, start: Int, count: Int,
after: Int
) {
}
override fun afterTextChanged(s: Editable) { // Check if edittext is empty
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
!(taskNameEditText.text.isBlank() || taskNameEditText.text.isEmpty())
}
})
dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
}
TasksRecyclerAdapter
class TasksRecyclerAdapter(private val context: Context) : RecyclerView.Adapter<TasksRecyclerAdapter.ViewHolder>() {
private var pomoTasksList: List<PomoTask> = emptyList()
private val layoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = layoutInflater.inflate(R.layout.tasks_list_item, parent, false)
return ViewHolder(itemView)
}
fun setTasksList(tasks: List<PomoTask>) {
pomoTasksList = tasks
notifyDataSetChanged()
}
override fun getItemCount() = pomoTasksList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val category = pomoTasksList[position]
holder.textTaskTitle?.text = category?.name
holder.textTaskDescription?.text = category?.desc
holder.taskOptMenu.setOnClickListener {
val popup = PopupMenu(context, holder.taskOptMenu)
//inflating menu from xml resource
//inflating menu from xml resource
popup.inflate(R.menu.category_menu)
//adding click listener
//adding click listener
popup.setOnMenuItemClickListener(object : MenuItem.OnMenuItemClickListener,
PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.app_settings -> {
Toast.makeText(context, "it works from recyclerview! :)", Toast.LENGTH_SHORT).show()
true
}
else -> false
}
}
})
popup.show()
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textTaskTitle = itemView.findViewById<TextView?>(R.id.tasks_list_item_title)
val textTaskDescription = itemView.findViewById<TextView?>(R.id.tasks_list_item_description)
val taskOptMenu: ImageView = itemView.findViewById(R.id.tasks_list_optmenu)
}