getDialog() in DialogFragment returns null - android-studio

Here's the code of my DialogFragment.kt
class CustomDialogCalendarFragment: DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding: FragmentCustomDialogCalendarBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_custom_dialog_calendar, container, false)
val application = requireNotNull(this.activity).application
//Create an instance of the ViewModelFactory
val dataSource = ReservationDatabase.getInstance(application).ReservationDatabaseDao
val viewModelFactory = CustomDialogCalendarViewModelFactory(dataSource)
//Get a reference to the ViewModel associated with this fragment
val customDialogViewModel =
ViewModelProvider(
this, viewModelFactory).get(CustomDialogCalendarViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.customDialogCalendarViewModel = customDialogViewModel
customDialogViewModel.navigateToSharedResourceCalendar.observe(viewLifecycleOwner) {
if (it == true) {
customDialogViewModel.doneNavigating()
}
}
binding.submitCreateEventButton.setOnClickListener { view: View ->
customDialogViewModel.onSetReservation(binding.nameEvent.text.toString())
this.findNavController().navigate(CustomDialogCalendarFragmentDirections.actionCustomDialogCalendarFragmentToSharedResourceCalendarFragment())
}
//HERE'S THE NULL POINTER EXCEPTION
getDialog()!!.getWindow()?.setBackgroundDrawableResource(R.drawable.round_corner)
return binding.root
}
override fun onStart() {
super.onStart()
val width = (resources.displayMetrics.widthPixels * 0.85).toInt()
//HERE'S THE NULL POINTER EXCEPTION
dialog!!.window?.setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
I don't understand why the getDialog() method returns null in every point of code on the onCreateView.
This class is called by another Fragment.
I have the same kind of DialogFragment in the project that does the same operations with no error.
So I don't know why this DialogFragment doesn't do the same.
Thank you!

Related

Jetpack Compose: force recomposition when a state changes from another activity

I have two Activities: ActivityOne.kt and ActivityTwo.kt. Both of them use Jetpack Compose to display an UI. In the first one, an item (let's say, a Text) has to be shown with variable showtText is true, and hidden when it's false. I achieve this by using:
#Composable
fun MyUI(){
AnimatedVisibility(visible = viewModel.showText) {
Text("Some text")
}
}
My variable showText is defined in a ViewModel such as:
val showText by mutableStateOf(false)
That way, anytime I change the value of showText within my ViewModel when ActivityOne.kt is visible, it appears or disaappears. What I want to achieve is the following:
From ActivityOne.kt the user can navigate to ActivityTwo.kt (with the first one running in background, it's not cleared from the stack).
There, there's a Switch that can toggle showText value.
When the user press "back", ActivityTwo.kt calls finish() and it dissapears, showing again ActivityOne.kt (it was in the stack).
If user has toggled showText value in ActivityTwo.kt, the text should be hidden or shown automatically, as the state of showText has changed.
The problem is that, although the value of showText does change, the UI in ActivityOne.kt does not respond to those changes. I've checked that recomposition of ActivityOne.kt's UI is not taking place after ActivityTwo.kt is finished, because it is not remembering the previous state of showText.
How could I achieve that? Thanks in advance!
EDIT 1
The problem is a little more difficult as I explained, but the basis are the same. Here is my complete code:
ActivityOne.kt:
class ActivityOne : ComponentActivity() {
private lateinit var vm: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vmFactory = MainViewModelFactory()
vm = ViewModelProvider(this, vmFactory).get(MainViewModel::class.java)
setContent {
MyTheme {
Surface {
MyUI()
}
}
}
}
#Composable
fun MyUI() {
AnimatedVisibility(visible = myPrefs.showText) {
Text("Some text")
}
}
}
MainViewModel.kt:
class MainViewModel : ViewModel() {
companion object {
val myPrefs by mutableStateOf( AppPrefs() )
}
}
ActivityTwo.kt:
class ActivityTwo : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
Surface {
MyUI2()
}
}
}
}
#Composable
fun MyUI2() {
val showText = remember {
mutableStateOf(MainViewModel.myPrefs.showText)
}
Switch(
checked = showText.value,
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs.showText = it
}
)
}
}
AppPrefs.kt:
class AppPrefs {
var showText: Boolean = false
}
The recomposition is not taking place because mutableStateOf is tracking the state of myPrefs which is never changed (the reference itself never changes).
You can achieve the recomposition in a few ways.
Here I will be assuming that your AppPrefs class either already contains more than one member/field, or that you would want to add more members to it in the future easily. That is why I added 2 more properties to it in the samples below and I only suggested solutions where adding more members won't affect the existing code.
Option 1
If you can change the AppPrefs class to track the state of each property individually, then make these changes
class MainViewModel : ViewModel() {
companion object {
val myPrefs = AppPrefs()
}
}
class AppPrefs {
var showText by mutableStateOf(false)
var prop2 by mutableStateOf(0)
var prop3: String? by mutableStateOf(null)
}
Everything else stays the same. Here the recomposition works again because now the state is tracked on members that are actually being changed.
Option 2
If you can't (or don't want to) have mutableStateOf inside AppPrefs you can change AppPrefs from a normal class to a data class. In that way you get the copy function automatically implemented (alongside equals, hashCode, toString and componentN for destructuring support).
class MainViewModel : ViewModel() {
companion object {
// the only change here is val -> var
var myPrefs by mutableStateOf(AppPrefs())
}
}
// data class instead of a normal class
data class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
)
In this case you have to also change how you change the value of myPrefs. This is what makes the recomposition work correctly again
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
In case you cannot use a data class, then you can still go with option 2, but you implement the copy function on your existing class
// normal class with a copy function
class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
) {
fun copy(
showText: Boolean = this.showText,
prop2: Int = this.prop2,
prop3: String? = this.prop3,
) = AppPrefs(showText, prop2, prop3)
}
As a bonus, if you go with any of the above solutions, you can even simplify your existing code for the MyUI2 composable and the recomposition will still work.
Example:
#Composable
fun MyUI2() {
val showText = MainViewModel.myPrefs.showText
Switch(
checked = showText,
onCheckedChange = {
// for Option 1
MainViewModel.myPrefs.showText = it
// for Option 2
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
)
}

Kotlin: Live data does not change Fragment UI when data changes

I am struggling to use Live data on an MVVM pattern. The app is supposed to:
Fetch data from an API (which it does correctly)
Store that data in the Live data object from the ViewModel
Then the fragment calls the Observer method to fill the recyclerView.
The problem comes in point 3, it does nothing, and I cannot find the solution.
Here is the relevant code. (If I'm missing something, I will try to answer as quickly as possible)
Main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: SharedViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Custom button to fetch data from api and log the Live Data value.
binding.refreshFab.setOnClickListener {
viewModel.fetchPlayerData()
Log.d("gabs", "${viewModel.livePlayerlist.value}")
}
}
}
ViewModel:
class SharedViewModel(app: Application): AndroidViewModel(app) {
// val playerDao = LaRojaDB.getDatabase(app).playerDao()
lateinit var playerList: Players
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
fun fetchPlayerData() {
CoroutineScope(Dispatchers.IO).launch {
val response = MyService.getLaRojaService().getAllPlayers()
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
val body = response.body()
if(!body.isNullOrEmpty()){
playerList = body
val playerArrayList = mutableListOf<Players.PlayersItem>()
playerList.forEach {
playerArrayList.add(it)
}
livePlayerlist.value = playerList
}
}
}
}
}
}
The fragment that displays the recycler view: (Fragment is already showing, I set up a textView as a title to make sure since I'm new using fragments as well.)
class PlayerListFragment : Fragment() {
private var _binding: FragmentPlayerListBinding? = null
private val binding get() = _binding!!
private val model: SharedViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentPlayerListBinding.inflate(inflater, container, false)
binding.rvPlayerList.layoutManager = LinearLayoutManager(activity)
----> // This is the observer that does not update the UI** <----
model.livePlayerlist.observe( viewLifecycleOwner, {
binding.rvPlayerList.adapter = PlayerAdapter(it)
})
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_player_list, container, false)
}
}
Thank you all in advance, hope I can finally learn what is causing the issue!
I think you don't need to switch Coroutine contexts. A few changes I'd expect if I were reviewing this code:
This should all be in the same IO context. You then postValue to your liveData.
fun fetchPlayerData() {
viewModelScope.launch(Dispatchers.IO) {
val xx = api.fetch()
...
_playerState.postValue(xx) //see below
}
}
Additionally, it's preferred not to expose mutable state, so your ViewModel should not expose the MutableLiveData (which shouldn't really be lazy). But it's also better to encapsulate the state in a sealed class:
//delete this
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
Should be: (names are just pseudo code, I have no idea what this code is about)
sealed class PlayerDataState {
data class ListAvailable(data: List<Players.PlayersItem>>): PlayerDataState
object Loading(): PlayerDataState
}
And your new LiveData:
private val _playerState = MutableLiveData<PlayerDataState>()
val playerState: LiveData<PlayerDataState>() get() = _playerState
Finally when observing from the UI, you just...
model.playerState.observe(viewLifecycleOwner, {
when (it) {
is Loading -> ...
is ListAvailable -> binding.rvPlayerList.adapter = PlayerAdapter(it.data)
}
}

Room cannot verify the data integrity and Crashes on Empty Data

I am working on Android Application in Which I am getting specific Data from Room Database by specific path in the Storage. My App Got Crashes as It does not have Any Data in the Storage and the Logcat gives me this..
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:154)
at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:195)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:428)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:317)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
at com.maximus.technologies.views.activities.scanneddatabase.TodoDaoScanned_Impl.getAllScan(TodoDaoScanned_Impl.java:152)
at com.maximus.technologies.views.fragments.scanhistorypackage.QRRetrievingScanClassPresenter$getAllDatFromDatabase$1.invokeSuspend(QRRetrievingScanClassPresenter.kt:29)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
The Above Error or crash Only occurs as the app dont have any data in Storage. But as I put a Data the Crash Problem Get Resolved.
I am not able to Understand what the Problem actually is...
Here is My Room Database Class..
#Database(
entities = [TodoEntity::class,TodoEntityScanned::class],
version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun TodoDao(): TodoDao
abstract fun TodoDaoScanned(): TodoDaoScanned
object DatabaseBuilder {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
if (INSTANCE == null) {
synchronized(AppDatabase::class) {
INSTANCE = buildRoomDB(context)
}
}
return INSTANCE!!
}
private fun buildRoomDB(context: Context) =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"mindorks-example-coroutines"
).build()
}
}
Room Database Retrieving Interface where app Crashes on getall()
override fun getAllDatFromDatabase(appDatabasescanned: AppDatabase) {
var list = listOf<TodoEntityScanned>()
try {
GlobalScope.launch(Dispatchers.Default) {
list = appDatabasescanned.TodoDaoScanned().getAllScan()
Log.d("hello","hello")
mView.showAllData(list)
}
}
catch (e:Exception){
Log.d("get hello",e.toString())
}
}
The getAll lies in Dao Class
interface TodoDao {
#Query("SELECT * FROM tablefilepaths")
fun getAll(): List<TodoEntity>
#Query("SELECT * FROM tablefilepaths WHERE imagespath LIKE :title")
fun findByTitle(title: String): TodoEntity
#Insert
fun insertpaths(todo: TodoEntity)
#Delete
fun deletepaths(todo: TodoEntity)
#Query("DELETE FROM tablefilepaths WHERE id = :noteId")
fun deleteNoteById(noteId: Int)
#Update
fun updateTodo(vararg todos: TodoEntity)}
Here is My Fragment Class Where I am Setting data in RecyclerView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerviewcreatehistory?.layoutManager = LinearLayoutManager(context)
recyclerviewcreatehistory?.setHasFixedSize(true)
filefetch()
customAdaptercreatehistory = CustomAdapterCreateHistory(this.context ?: return, charItemcreate!!,this)
recyclerviewcreatehistory?.adapter = customAdaptercreatehistory
}
fun filefetch() {
val noteDatabase: AppDatabase = AppDatabase.DatabaseBuilder.getInstance(requireContext())
retrivingpresenter = QRRetrievingClassPresenter(this)
retrivingpresenter!!.getAllDatFromDatabase(noteDatabase)
}
override fun showAllData(note_list: List<TodoEntity>) {
if (note_list is ArrayList<*>) {
val arraylist = note_list as ArrayList<TodoEntity>
charItemcreate=arraylist
}
if (charItemcreate.isEmpty()){
}else{
customAdaptercreatehistory?.updateUsers(note_list as ArrayList<TodoEntity>)
customAdaptercreatehistory?.notifyDataSetChanged()
// Log.d("hello", note_list[0].imagesPathData)
}
}
You have to do some checks in your getAllDatFromDatabase() inside your coroutine. I guess list variable equals null or something like that. You should check if there are any files and if not you need to put there something else.

RecyclerView(Kotlin): Adding an swipe to delete functionality on a ToDo app with data in SQL database

I am new to kotlin and android studio and currently I am trying to build an todo list applications with my own ideas. Its mostly done but I have to add edit and delete functionality to the tasks that user adds. The tasks that user adds are stored on the device using SQLiteDatabase. This is the base swipe to delete class that I wrote:
abstract class SwipeToDelete(context: Context, dragDir: Int, swipeDir: Int): ItemTouchHelper.SimpleCallback(dragDir, swipeDir) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
TODO("Not yet implemented")
}
}
This is the delete functionality that I have added which works great with a button:
fun deleteToDo(todoId: Long){
val db = writableDatabase
db.delete(TABLE_TODO_ITEM,"$COL_TODO_ID=?", arrayOf(todoId.toString()))
db.delete(TABLE_TODO,"$COL_ID=?", arrayOf(todoId.toString()))
}
This is the recyclerview adapter that I am using:
class ItemAdapter(val context: Context,val dbHandler: DBHandler, val list: MutableList<ToDoItem>) :
RecyclerView.Adapter<ItemAdapter.ViewHolder>(){
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.rv_child_item,p0,false))
}
override fun onBindViewHolder(holder: ViewHolder, p1: Int) {
holder.itemName.text = list[p1].itemName
holder.itemName.isChecked = list[p1].isCompleted
holder.itemName.setOnClickListener{
list[p1].isCompleted = !list[p1].isCompleted
dbHandler.updateToDoItem(list[p1])
}
}
override fun getItemCount(): Int {
return list.size
}
class ViewHolder(v : View) : RecyclerView.ViewHolder(v){
val itemName : CheckBox = v.findViewById(R.id.cb_item)
}
}
but for some reason I cannot make it work when I try to call the delete function in this Swipetodelete object:
val item = object : SwipeToDelete(this,0,ItemTouchHelper.LEFT){
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}
I also want to add the edit functionality but if i can get this delete functionality to work i can add it.
Since the SwipeToDelete is an abstract class, you can override the onSwiped function on your Fragment/Activity Class.
You can modify your SwipeToDelete class to:
abstract class SwipeToDelete(context: Context, dragDir: Int, swipeDir: Int): ItemTouchHelper.SimpleCallback(dragDir, swipeDir) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
}
And then override the onSwiped function inside your fragment/activity and attach it to your recyclerview:
val item = object : SwipeToDelete(this,0,ItemTouchHelper.LEFT){
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
deleteToDo(todoId)
}
}
ItemTouchHelper(item).attachToRecyclerView(recycler)

Unconditional layout inflation from view adapter. kotlin

I am getting following warning in Android Studio:
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling." on inflater.inflate(R.layout.animal_ticket, null) line.
How do I fix the warning? I am not able to find solution for that problem.
Thanks!
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val animal = listOfAnimals[p0]
val inflater = LayoutInflater.from(context)
var holder = ViewHolder()
val myView = inflater.inflate(R.layout.animal_ticket, null)
myView.tvName.text = animal.name!!
myView.tvDes.text = animal.description!!
myView.jvAnimalImage.setImageResource(animal.image!!)
return myView
}
If there was previously another View of the same view type (getItemViewType(position: Int) returned the same value) that scrolled off screen, the list view may pass in that instance as the second parameter to getView(). It will be faster to reuse that view than to inflate a new one.
You should also use the ViewHolder to cache things about the view, such as the relatively expensive findViewById(). You can attach it to and retrieve it from the view by tag.
override fun getView(position: Int, convertView: View?, parent: ViewGroup?) {
val animal = listOfAnimals[position]
val myView = convertView ?:
LayoutInflater.from(context).inflate(R.layout.animal_ticket, parent, false)
val holder = myView.tag as? ViewHolder ?: ViewHolder(myView)
myView.tag = holder
holder.tvName.text = animal.name!! // etc
}
class ViewHolder(view: View) {
val tvName: TextView = view.name // etc
}
For any RecyclerView, you need your own ViewHolder class that has all of the views listed inside animal_ticket.
Basically, it works like this:
1) Create ViewHolder that 'holds' all of the views of the item you want to display;
2) Bind the ViewHolder to the RecyclerView and assign values to the views inside;
Here is a example adapter I wrote :
class MyActivity : Activity() {
//users is the list we're going to use to get information for the views
val users = ArrayList<User>()
//...getting user information
//.. your activity stuff here
//Creating our adapter
/*
Note that to extend the RecyclerView.Adapter, you need to specify a
ViewHolder. The code becomes much easier to manage if you just put the
ViewHolder inside your adapter.
*/
inner class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
//This is the view holder
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//Here you declare your views and get them from the itemView
//The itemView is one that is passed each time to the RecyclerView
//(the items inside your XML layout file)
internal val userImageView = itemView.findViewById(R.id.userImage)
internal val userFullName = itemView.findViewById(R.id.userName)
}
//This is where you return your own ViewHolder with your layout
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
//user_list_item.xml is below
val itemView = layoutInflater.inflate(R.layout.user_list_item, parent, false))
return MyViewHolder(itemView)
}
//In here is where you want to set your values for the views
override fun onBindViewHolder(holder: SentRequestViewHolder, position: Int) {
val currentUser = users[position]
holder.userImage.drawable = currentUser.drawable
holder.userFullName.text = currentUser.name
}
//You must override this method as well for the adapter to work properly
override fun getItemCount() = users.size
}
You have to override these methods when using a RecyclerView.Adapter
Here is the user_list_item.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="#+id/userImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/userName"
android:layout_below="#id/userImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>

Resources