Optional insert into database? - android

I apologize if my title was a bit general, but I currently have a journal app where the user can pick an image when making a new entry and insert it into a database, which gets shown in a recyclerview later.
class addFragment : Fragment(), EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {
lateinit var bitmap: Bitmap
private val mJournalViewModel: JournalViewModel by viewModels()
private val mSharedViewModel: SharedViewModel by viewModels()
var currentDate: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_add, container, false)
// set menu
setHasOptionsMenu(true)
view.mood_spinner.onItemSelectedListener = mSharedViewModel.listener
// Set date and time for currentDate
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm a")
currentDate = sdf.format(Date())
// Opens gallery when image button clicked, gets image
view.image_et.setOnClickListener {
readStorageTask()
//Intent to pick image
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(intent, 1001)
}
return view
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.clear()
}
// Handle result of picked image
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == 1001) {
// Converts image URI to bitmap
if (data != null && data.data != null) {
val uri = data.data!!
val inputStream = requireContext().contentResolver.openInputStream(uri)
val cursor = requireContext().contentResolver.query(uri, null, null, null, null)
cursor?.use { c ->
val nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (c.moveToFirst()) {
val name = c.getString(nameIndex)
inputStream?.let { inputStream ->
// create same file with same name
val file = File(requireContext().cacheDir, name)
val os = file.outputStream()
os.use {
inputStream.copyTo(it)
}
val bitmapUnprocessed = BitmapFactory.decodeFile(file.absolutePath)
bitmap = resizedBitmap(bitmapUnprocessed)
preview_image.setImageBitmap(bitmap)
image_et.text = "Image picked!"
}
}
}
}
}
}
// Creates check mark at the top of the fragment
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.add_fragment_menu, menu)
}
// If the check mark is clicked, entry gets added
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if(item.itemId == R.id.menu_add) {
insertDataToDb()
}
return super.onOptionsItemSelected(item)
}
// uses below function to check if text is empty, and gets values from add fragment
private fun insertDataToDb() {
val mTitle = title_et.text.toString()
val mMood = mood_spinner.selectedItem.toString()
val mDescription = description_et.text.toString()
val mDate = currentDate.toString()
val validation = mSharedViewModel.verifyDataFromUser(mTitle, mDescription)
if(validation) {
val newData = JournalData(
0,
mTitle,
parseMood(mMood),
mDescription,
mDate,
bitmap
)
mJournalViewModel.insertData(newData)
Toast.makeText(requireContext(), "New entry added!", Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_addFragment_to_listFragment) // Jetpack Navigation
} else {
Toast.makeText(requireContext(), "You have some empty fields.", Toast.LENGTH_SHORT).show()
}
}
And this is the database these values are getting inserted into:
#Entity(tableName = "journal_table")
#Parcelize
data class JournalData(
#PrimaryKey(autoGenerate = true)
var id: Int,
var title: String,
var mood: Mood,
var description: String,
var date: String,
var image: Bitmap
): Parcelable
Issue
If the user doesn't pick an image, my app crashes and doesn't work in general (as expected). Is there any way to make it so that the bitmap/image in each entry can just stay as null or empty when all the other values are being inserted without causing any errors?
Basically, I'm trying to make attaching an image when making a journal entry optional, but I'm not sure how I could do this.
I'm pretty new to using room databases and have been trying to figure out a solution for this for a few days now, so any help would be greatly appreciated!

If your image is not taken then you can stop the insertion by adding check before db insertion OR if you want to insert even if the image is null then you can simply add the default value of bitmap in room data entity class
var image: Bitmap?=null

Related

Can not perform this action after onSaveInstanceState when closing an activity

I know this error is very common, but I couldn't find a solution to my problem in the way too many places this error is brought up.
I'm developing an app in order to store and sort TV shows. I've got a main activity with some fragments, with a HomeFragment which is the home page, with an 'Add show' button, and below a recyclerView with all my shows.
When clicking on the 'Add show' button, I start a new activity in order to fill a form and then create the show with the provided informations. No problem here, that works as it should. Now I'm trying to add the possibility to edit the shows by clicking on them in the recyclerView I talked about above. This also brings up the same activity as the 'Add show' button, but this time with the show informations.
And this is from this page that the problem seems to be coming. In the form activity, I have an button in which I pick an image for the show. When editing the show, if I change the image, I get no error, but if I change something else, for example the name, without changing this image, when clicking the confirm button, the show is correctly edited but the app crashes with the java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState error.
The error seems to be coming from the fragment part, with the transaction being unable to commit (I've searched for a while so I began to understand why that wasn't working, but couldn't determine which part of the code makes it this way). Here is the fragment:
class HomeFragment(private val context: MainActivity) : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
view.findViewById<Button>(R.id.button_add_show).setOnClickListener{
startActivity(Intent(context, AddShowActivity::class.java))
}
val verticalRecyclerView = view.findViewById<RecyclerView>(R.id.vertical_recycler_view)
verticalRecyclerView.adapter = ShowAdapter(context, showList, R.layout.item_show)
return view
}
}
And here the MainActivity part where it's loaded:
private fun loadFragment(fragment: Fragment){
// Load repository
val repo = ShowRepository()
// Update shows list
repo.updateData{
// Inject fragment into fragment_container
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
Here is the code of my AddShowActivity, which renders the form to fill:
class AddShowActivity : AppCompatActivity() {
private var fileImage: Uri? = null
private lateinit var uploadedImage: ImageView
private lateinit var editTextName: EditText
private lateinit var editTextNote: EditText
private lateinit var editTextDescription: EditText
private lateinit var editTextReview: EditText
private lateinit var datePicker: DatePicker
private var currentShow: ShowModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_show)
setupComponents()
setupButtons()
// Get show when editing
if(intent.extras != null){
val position = intent.extras!!.getInt("position")
currentShow = showList[position]
}
initializeComponents()
}
private fun setupComponents() {
editTextName = findViewById(R.id.name_input)
editTextNote = findViewById(R.id.note_input)
editTextDescription = findViewById(R.id.description_input)
editTextReview = findViewById(R.id.review_input)
uploadedImage = findViewById(R.id.preview_image)
datePicker = findViewById(R.id.watch_date_input)
}
private fun setupButtons(){
val pickupImageButton = findViewById<Button>(R.id.upload_image_button)
pickupImageButton.setOnClickListener{
pickupImage()
}
val confirmButton = findViewById<Button>(R.id.confirm_button)
confirmButton.setOnClickListener{
sendForm()
val toastText = when(currentShow){
null -> "Show added"
else -> "Show edited"
}
Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show()
this.finish()
}
}
#SuppressLint("NewApi")
private fun initializeComponents() {
if(currentShow != null){
editTextName.setText(currentShow!!.name)
editTextNote.setText(currentShow!!.note.toString())
editTextDescription.setText(currentShow!!.description)
editTextReview.setText(currentShow!!.review)
Glide.with(this).load(Uri.parse(currentShow!!.imageUrl)).into(uploadedImage)
}
}
private fun sendForm(){
val repo = ShowRepository()
if(fileImage == null)createShow(repo)
else{
if(currentShow != null)repo.deleteImage(currentShow!!)
repo.uploadImage(fileImage!!){
createShow(repo)
}
}
}
private fun createShow(repo: ShowRepository){
val showName = editTextName.text.toString()
val showNote = parseInt(editTextNote.text.toString())
val description = editTextDescription.text.toString()
val review = editTextReview.text.toString()
val showWatchDate = getWatchDate(datePicker)
val downloadImageUrl = downloadImageUri.toString()
val show = ShowModel(UUID.randomUUID().toString(), showName, showWatchDate, showNote, downloadImageUrl, description, review)
if(currentShow != null){
show.id = currentShow!!.id
repo.updateShow(show)
}
else repo.insertShow(show)
}
private fun getWatchDate(datePicker: DatePicker): String {
var day = datePicker.dayOfMonth.toString()
if(day.toInt() < 10)day = "0$day"
var month = (datePicker.month + 1).toString()
if(month.toInt() < 10)month = "0$month"
val year = datePicker.year.toString()
return "$day-$month-$year"
}
private fun pickupImage(){
val intent = Intent()
intent.type = "image/"
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), 47)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK && requestCode == 47){
if(data == null || data.data == null)return
fileImage = data.data
uploadedImage.setImageURI(fileImage)
}
}
}
Here the ShowRepository, which handles the communication with the Firebase database:
class ShowRepository {
object Singleton{
// Link to bucket
private val BUCKET_URL: String = "gs://tv-memories.appspot.com"
// Storage connexion
val storageReference = FirebaseStorage.getInstance().getReferenceFromUrl(BUCKET_URL)
// Database connexion
val databaseRef = FirebaseDatabase.getInstance().getReference("shows")
// List containing all shows
val showList = arrayListOf<ShowModel>()
// Contains current image link
var downloadImageUri: Uri? = null
}
fun updateData(callback: () -> Unit){
// Absorb data from databaseRef
databaseRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
// Remove old shows
showList.clear()
// Get list
for(ds in snapshot.children){
//Build show object
val show = ds.getValue(ShowModel::class.java)
// Verify show isn't null
if(show != null){
// Add show to the list
showList.add(show)
}
}
// Activate callback
callback()
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
// Upload files on storage
fun uploadImage(file: Uri, callback: () -> Unit){
val fileName = UUID.randomUUID().toString() + ".jpg"
val ref = storageReference.child(fileName)
val uploadTask = ref.putFile(file)
uploadTask.continueWithTask(Continuation<UploadTask.TaskSnapshot, Task<Uri>>{ task ->
if(!task.isSuccessful){
task.exception?.let{throw it}
}
return#Continuation ref.downloadUrl
}).addOnCompleteListener{ task ->
if(task.isSuccessful){
downloadImageUri = task.result
callback()
}
}
}
fun deleteImage(show: ShowModel){
val photoRef: StorageReference = FirebaseStorage.getInstance().getReferenceFromUrl(show.imageUrl)
photoRef.delete()
}
fun updateShow(show: ShowModel) = databaseRef.child(show.id).setValue(show)
fun insertShow(show: ShowModel) = databaseRef.child(show.id).setValue(show)
fun deleteShow(show: ShowModel){
databaseRef.child(show.id).removeValue()
deleteImage(show)
}
}
And the full traceback of the error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: fr.steph.showmemories, PID: 18296
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844)
at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884)
at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329)
at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294)
at fr.steph.showmemories.MainActivity$loadFragment$1.invoke(MainActivity.kt:49)
at fr.steph.showmemories.MainActivity$loadFragment$1.invoke(MainActivity.kt:44)
at fr.steph.showmemories.ShowRepository$updateData$1.onDataChange(ShowRepository.kt:61)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7078)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
I finally found a solution to my problem, which is to replace transaction.commit() by transaction.commitAllowingStateLoss() in my loadFragment() method in case the state has been saved.
I then get
private fun loadFragment(fragment: Fragment){
// Load repository
val repo = ShowRepository()
// Update shows list
repo.updateData{
// Inject fragment into fragment_container
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.addToBackStack(null)
if(supportFragmentManager.isStateSaved)transaction.commitAllowingStateLoss()
else transaction.commit()
}
}

How to show image from an external database (kotlin)

Hello guys I want to make app with an External Database, I made my Database with SQLite browser, Put my image to drawable, I need to show image and text but it just shows text I mean it works
(Image does not show also this is not any error log about it )
For more guidance I have some code in Adapter
also I put gitlab link
any help would be appreciate. Thank you
class model {
var id:Int?=null
var name:String?=null
var image:String?=null
}
object info_db {
val dbName="country"
val dbversion=2
val dbId="id"
val dbcname="name"
val dbimage="image"
val packageData="data/data/com.alialim.countrylist_sample/databases/"
val source ="country.sqlite"
}
class Database(val context: Context): SQLiteOpenHelper(context,info_db.dbName,null,info_db.dbversion) {
override fun onCreate(p0: SQLiteDatabase?) {
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
init {
initDatabase()
}
private fun initDatabase() {
var file = File(info_db.packageData)
if (file.exists()) {
} else {
file.mkdirs()
}
file = context.getDatabasePath(info_db.dbName)
if (file.exists()) {
} else {
copyDataBase()
}
}
private fun copyDataBase() {
val inputStream = context.assets.open(source)
val outFile = packageData + dbName
val uotPutStream = FileOutputStream(outFile)
val buffer = ByteArray(1024)
var lenght = 0
while (inputStream.read(buffer).also({ lenght = it }) > 0)
uotPutStream.write(buffer, 0, lenght)
uotPutStream.flush()
uotPutStream.close()
inputStream.close()
}
#SuppressLint("Range")
fun getAll(): ArrayList<model> {
val db = readableDatabase
val query = "SELECT * FROM countrylist"
var list: ArrayList<model> = ArrayList()
val cursor = db.rawQuery(query, null)
if (cursor.moveToFirst()) {
do {
val lmodel = model()
lmodel.id=cursor.getInt(cursor.getColumnIndex(info_db.dbId))
lmodel.name=cursor.getString(cursor.getColumnIndex(info_db.dbcname))
lmodel.image=cursor.getString(cursor.getColumnIndex(info_db.dbimage))
list.add(lmodel)
Log.d(
"massage",
"log Database name ==>${lmodel.name} and category ==> ${lmodel.image} "
)
} while (cursor.moveToNext())
}
cursor.close()
db.close()
return list
}
}
object Base: Application() {
var database: Database?=null
var activity: Activity?=null
override fun onCreate() {
super.onCreate()
}
}
class AdapterCountry(val context: Context, val countrylists:ArrayList<model>): RecyclerView.Adapter<AdapterCountry.HolderCountry>() {
class HolderCountry(itemView: View): RecyclerView.ViewHolder(itemView){
val imagerecy=itemView.findViewById<ImageView>(R.id.recycler_main_image)
val txtfname=itemView.findViewById<TextView>(R.id.recycler_main_text)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HolderCountry {
val layout= LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false)
return HolderCountry(layout)
}
override fun onBindViewHolder(holder: HolderCountry, position: Int) {
val countrylist=countrylists.get(position)
holder.txtfname.text=countrylist.name
//code that should display the images but dosent work
val img=countrylist.image
val Image: Int = Base.activity!!.getResources()
.getIdentifier(img, "drawable", Base.activity!!.getPackageName())
holder.imagerecy.setImageResource(Image)
//////////////
}
override fun getItemCount()=countrylists.size
}
class MainActivity : AppCompatActivity() {
var btnclick: Button?=null
var recycler: RecyclerView?=null
var adaptecounry: AdapterCountry?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnclick=findViewById(R.id.showcountrye)
recycler=findViewById(R.id.mainactivity_recy_image)
Base.activity=this
Base.database= Database(this)
btnclick!!.setOnClickListener {
recycler!!.setHasTransientState(true)
recycler!!.layoutManager= LinearLayoutManager(Base.activity)
val list= Base.database!!.getAll()
adaptecounry = AdapterCountry(Base.activity!!,list)
recycler!!.adapter=adaptecounry
}
}
}
SQLitebrowser
drawable
please visit : https://gitlab.com/salin1/country_list/-/tree/master
Hello everybody I could fix my problem because I was Short of time anyway
I writ my correct code here maybe one day somebody like me needed it, My cod is correct but you need to put this code in your RecyclerView Adapter
override fun onBindViewHolder(holder: HolderCountry, position: Int) {
val countrylist=countrylists.get(position)
Log.i("imaaagee","${countrylist.image}")
holder.txtfname.text=countrylist.name
val img=countrylist.name
img!!.trim()
val identifier=context.resources.getIdentifier(img,"drawable",
context!!.getPackageName())
holder.imagerecy.setImageResource(identifier)
}
also I put link and image if you look at my database image you see in image column I write image name with format (.png) but its mistake because getIdentifier does not need image format the all thing you should do is put your image to drawable and recover with your adapter code
for more understanding I put my gitlab link you can download and see code
if you have any question just ask I will respond
Thank you

moving the function from preferenceFragment to the second fragment

Well, I have a question, how to pass a function or its value to the second fragment? I am using the MVVM structure? I am exactly making an application in which in settingsFragment you select the csv file you want to read and send the result to the fragment with the graph and draws the graph for you. I've already done selecting the csv file but don't know how to read it and transfer the data from the file to the second fragment? Take a look at my code, if there is anything incomprehensible in the question or code, ask
SettingsFragment
class SettingsFragment : PreferenceFragmentCompat() {
private val SETTINGS_DEBUG = "PROFILE_DEBUG"
private var resolver = requireActivity().contentResolver
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val myPref: Preference? = findPreference("load_csv_file") as Preference?
myPref?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"
startActivity(intent)
}catch (exc: Exception) {
Log.d(SETTINGS_DEBUG, exc.message.toString())
}
true
}
fun readCSV(uri: Uri?): List<String> {
if (uri != null) {
val csvFile = resolver.openInputStream(uri)
val isr = InputStreamReader(csvFile)
return BufferedReader(isr).readLines()
}
return Collections.emptyList()
}
}
}
ChartFragment
class ChartFragment : Fragment() {
private var _binding: FragmentChartBinding? = null
private val binding get() = _binding!!
private var resolver = requireActivity().contentResolver
private val lineChartVm by viewModels<ChartViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentChartBinding.inflate(inflater, container, false)
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
val lines = readCSV(uri)
val newEntries = lines.map { line -> toEntry(line) }.groupingBy { entry -> entry.x }
.reduce { _, accumulator, element -> if (accumulator.y > element.y) accumulator else element }.values
val lineChart = binding.lineChart
val vl = LineDataSet(newEntries.toList().take(4000), "cost")
vl.setDrawValues(false)
vl.setDrawFilled(true)
vl.lineWidth = 1.5f
vl.fillColor = R.color.gray
vl.fillAlpha = R.color.red
vl.setDrawCircles(false)
lineChart.data = LineData(vl)
lineChart.notifyDataSetChanged()
lineChart.animateX(1800, Easing.EaseInExpo)
lineChart.description.isEnabled = false
lineChart.isHighlightPerDragEnabled = false
lineChart.isScaleYEnabled = false
lineChart.axisRight.isEnabled = false
}
val markerView = CustomMarker(activity?.applicationContext, R.layout.marker_view)
binding.lineChart.marker = markerView
return binding.root
}
private fun toEntry(line: String): Entry {
val split = line.split(";")
val time = split[1]
// idx 01234 012345 l:5 lub 6
// val 84504 165959
// 0, 3 - 845
val secondsStartIdx = time.length - 2
val minutesStartIdx = time.length - 4
val hoursStartIdx = (time.length - 6).coerceAtLeast(0)
val hour = time.substring(hoursStartIdx, hoursStartIdx + time.length - 4)
val minutest = time.substring(minutesStartIdx, minutesStartIdx + 2)
val seconds = time.substring(secondsStartIdx, secondsStartIdx + 2)
val newTime =
hour.toFloat() * 10000 + (minutest.toFloat() * 100 / 60).toInt() * 100 + (seconds.toFloat() * 100 / 60).toInt()
return Entry(newTime, split[2].toFloat())
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
There is not a lot of detail in the question, so I can only give a very general example and use made up class names and function arguments. This should give you an idea of the basic pattern to use.
Use a shared ViewModel in both Fragments. That means it is scoped to the Activity instead of the Fragment lifecycle. You can do this by using the activityViewModels() property delegate. In both Fragments, it would look like this:
val myViewModel: MyViewModel by activityViewModels()
In MVVM, your ViewModel class should be responsible for finding the file and reading it (or delegating to some other class to do so), so your readCSV function should be moved there. And it can parse the results into however you need them. File reading needs to be done on a background thread, and this is most easily done using coroutines started from viewModelScope. Results can be published to a LiveData.
class MyViewModel : ViewModel() {
private val mutableCsvResultLiveData = MutableLiveData<List<String>>()
val csvResultLiveData: LiveData<List<String>> get() = mutableCsvResultLiveData
fun readCSV(uri: Uri?, resolver: ContentResolver) {
uri ?: return // I would just make uri non-nullable so you don't need this
viewModelScope.launch(Dispatchers.IO) {
try {
val csvFileInputStream = resolver.openInputStream(uri)
check(csvFileInputStream != null) { "ContentResolver provider crashed" }
val isr = InputStreamReader(csvFileInputStream)
val result = BufferedReader(isr).readLines()
mutableCsvResultLiveData.postValue(result)
} catch (e: Exception) {
Log.e("readCSV", "Failed to read file", e)
return#launch
}
}
}
}
Then one fragment can initiate the file operation and parsing by calling myViewModel.readCSV(). The other fragment can observe the LiveData to react to the data when it arrives:
myViewModel.csvResultLiveData.observe(viewLifecycleOwner) { csvLines ->
// do something with csvLines, a List<String>
}

Android Room getById(id: Int) query returns a valid object when called from first fragment, but returns null when called from all the others

I am working on my school project, and I started getting this weird Room behavior. I have to admit, that everything used to work correctly, but after changing some things it stopped, and now it doesn't work even though I returned almost everything to where it was.
Here's my UserDao.kt:
#Dao
interface UserDao {
#Query("SELECT * FROM $USER_TABLE_NAME")
fun getAll(): LiveData<List<User>>
#Query("SELECT * FROM $USER_TABLE_NAME WHERE id = :id")
fun getById(id: Int): LiveData<User>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(user: User)
#Update
fun update(user: User)
#Query("UPDATE $USER_TABLE_NAME SET pictureAddress = :image WHERE id = :id")
fun updateImageWhereId(id: Int, image: String)
#Delete
fun delete(user: User)
}
Here's the LoginFragment.kt, the first fragment, that gets loaded, when application starts. Here I check, if there is a user in the database and in case there is, I check, if passwords match. This is the place, where the query returns the user it supposed to be returning, and everything works.
class LoginFragment : Fragment() {
private lateinit var binding: FragmentLoginBinding
private lateinit var model: MainViewModel
private val navigation: INavigationCallback by lazy {
activity as INavigationCallback
}
data class IdPassword(var id: String = "", var password: String = "", var isCorrect: Boolean = true)
private val idPassword: IdPassword = IdPassword()
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
DataBindingUtil.inflate<FragmentLoginBinding>(inflater,
R.layout.fragment_login,
container, false).run {
binding = this
model = activity!!.let {
ViewModelProviders
.of(it, MainViewModel.Factory(it.application))
.get(MainViewModel::class.java)
}
lifecycleOwner = this#LoginFragment
idPassword = this#LoginFragment.idPassword
navigation = this#LoginFragment.navigation
applyLoginButton.setOnClickListener { tryLogin() }
return root
}
private fun tryLogin() {
//databaseData.getUserById - is basically a wrapper
model.databaseData.getUserById(idPassword.id.toInt()).observe(activity!!, Observer {
if(it != null && it.password == idPassword.password){
model.activeUserId = it.id //this stores active user's id
navigation.navigateTo(R.id.action_loginFragment_to_mainMenuFragment)
} else {
binding.errorLogin.visibility = View.VISIBLE
}
})
}
}
After logging in successfully, we get to the main menu screen, where I need once more to acquire user, to get his name and profile picture displayed.
Here's the MainMenuFragment.kt , the place, where the same query as above returns null. I also tried testing this, so read comments to understand better, what I have done:
class MainMenuFragment : Fragment() {
private lateinit var binding: FragmentMainMenuBinding
private val viewEffect: MainMenuViewUtils by lazy {
MainMenuViewUtils(binding.userNameSmall, binding.mainLinearLayoutTitle)
}
private val globalLayoutListener = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
viewEffect.adjustCardSize(activity!!, binding.todayStatsCard)
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? = DataBindingUtil
.inflate<FragmentMainMenuBinding>(inflater,
R.layout.fragment_main_menu,
container, false).run {
binding = this
lifecycleOwner = this#MainMenuFragment
navigation = activity as INavigationCallback
viewModel = activity!!.let {
ViewModelProviders
.of(it, MainViewModel.Factory(it.application))
.get(MainViewModel::class.java)
}
viewModel?.let {
user = it.activeUser
it.setUpMainMenuObservers()
}
mainMenuAppbar.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
viewEffect.onOffsetChanged(appBarLayout, verticalOffset)
})
profilePicture.setOnClickListener {
Dialogs.profilePictureDialog(activity!!, viewModel!!).show()
}
mainMenuToolbar.setUpMainMenuToolbar(navigation!!)
root.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
viewEffect.startAlphaAnimation(userNameSmall, 0, View.INVISIBLE)
setHasOptionsMenu(true)
return root
}
private fun setUpBluetooth(): Boolean {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
activity?.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
return true
}
private fun MainViewModel.setUpMainMenuObservers(){
//this is the original call which doesn't work
activeUser.observe(activity!!, Observer {
log("Active user id = $activeUserId") //this prints correct id
if(it == null) log("USER IS NULL")
else log("USER : ${it.name}") // and this prints that user is null, so it wasn't found
tryCatch {
binding.profilePicture.setImageBitmap(getProfilePicture(it, 256))
}
})
//this was added for testing purposes. It returns a list of all users in the db.
databaseData.getAllUsers.observe(activity!!, Observer {
log("ALL USERS:")
it.forEach{user: User ->
log("NAME : ${user.name}, ID = ${user.id}") //this prints each user with the correct name and id
}
log("Active user id = $activeUserId") //this prints correct active user id
val user = it.first { id == activeUserId } // on this line app crashes, as if 1 != 1.
log("USER : ${user.name}")
tryCatch {
binding.profilePicture.setImageBitmap(getProfilePicture(user, 256))
}
})
bluetoothData.steps.observe(activity!!, Observer {
binding.stepsTodayCounter.text = it.toString()
})
bluetoothData.location.observe(activity!!, Observer {
val text = "${lastDayData.getLastDayDistanceFormatted()} Km"
binding.distanceTodayCounter.text = text
})
bluetoothData.isBluetoothConnected.observe(activity!!, Observer {
when (it) {
true -> binding.changeOnBTConnected("Connected!", R.drawable.bt_connected_icon)
false -> binding.changeOnBTConnected("Not Connected!", R.drawable.bt_disconnected_icon)
}
})
}
private fun FragmentMainMenuBinding.changeOnBTConnected(changeText: String, changeDrawable: Int){
connectionStatus.text = changeText
mainMenuToolbar.menu?.findItem(R.id.menu_bluetooth)?.icon =
activity!!.getDrawable(changeDrawable)
Toast.makeText(activity!!, "You are $changeText!", Toast.LENGTH_LONG).show()
}
private fun Toolbar.setUpMainMenuToolbar(navigation: INavigationCallback) {
inflateMenu(R.menu.main_view_menu)
menu.findItem(R.id.menu_bluetooth).setOnMenuItemClickListener {
setUpBluetooth()
}
menu.findItem(R.id.menu_settings).setOnMenuItemClickListener {
navigation.navigateTo(R.id.action_mainMenuFragment_to_settingsFragment)
true
}
}
}
So from the comments you can understand how weird it is. So in testing query, I would get prints in console like:
NAME : Slava Simonov, ID = 1
NAME : Uzi, ID = 11
Active user id = 1
Shutting down VM
FATAL EXCEPTION: main
...
I hope someone will be able to help me, because I have run out of ideas, why that could happen. It feels like some kind of bug. If you need some other pieces of my code, I can attach it, just feel free to ask.
Thanks to everybody in advance.

Could not complete scheduled request to refresh entries. ClientErrorCode: 3 Android Kotlin

Let me get straight to the point here the error in the logcat is:
Could not complete scheduled request to refresh entries. ClientErrorCode: 3
I have tested the Realm() part of the code and it fetched the right data. Basically, the app just crashes when it loads that Activity. All Im trying to do right now is post the itemName in each cell. If you guys need the logcat, just say so and I'll post it. Any other details needed too.
This is the code for my Activity with a recyclerView with just an ImageView and a TextView in each cell.:
class EssentialsActivity : AppCompatActivity() {
var category: String? = null
val realmtypeFunctions = RealmTypeFunctions()
var realmResults: RealmResults<ChattRItem>? = null
var chattRItemList = mutableListOf<ChattRItem>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_essentials)
//init realm
Realm.init(this)
category = "People"
recyclerView_Essentials.setBackgroundColor(Color.CYAN)
recyclerView_Essentials.layoutManager = GridLayoutManager(this, 3)
// AsyncTask.execute {
category?.let {
loadFromRealm(it)
}
// }
this.runOnUiThread {
recyclerView_Essentials.adapter = EssentialsAdapter(chattRItemList)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.categories, menu )
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val intent: Intent?
intent = Intent(this, AddItemActivity::class.java)
intent.putExtra("category", category)
startActivity(intent)
// when (item?.itemId) {
// R.id.essentials_menu_item -> {
// intent = Intent(this, EssentialsActivity::class.java)
// startActivity(intent)
// }
// R.id.addItem_menu_item -> {
// intent = Intent(this, AddItemActivity::class.java)
// startActivity(intent)
// }
// else -> return false
// }
return super.onOptionsItemSelected(item)
}
private fun loadFromRealm(category: String){
val realm = Realm.getDefaultInstance()
try {
val query: RealmQuery<ChattRItem>? = realm.where(ChattRItem::class.java).equalTo("itemCategory", category)
val result: RealmResults<ChattRItem>? = query?.findAll()
result?.let {
for (i in it) {
println(i.itemName)
chattRItemList.add(i)
}
println(chattRItemList.count())
}
} finally {
realm.close()
}
}
}
class EssentialsAdapter(private val chattRItemList: List<ChattRItem>): RecyclerView.Adapter<CustomViewHolder>(){
//realm class variable here to be displayed
/* var essentials = array of realm essential item */
// var essentialsActivity = EssentialsActivity()
//number of items
override fun getItemCount(): Int {
// return 12 //return realm items count
return this.chattRItemList.size
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
// holder.itemView.textView_essentials_name.text = "Essentials Item"
val chattRItem = chattRItemList.get(position)
// holder.itemView.textView_essentials_name.text = chattRItem.itemName
holder.bind(chattRItem)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder{
// how do we create a cell view
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.essentials_cells_layout, parent, false)
return CustomViewHolder(view = cellForRow)
}
}
class CustomViewHolder(view: View): RecyclerView.ViewHolder(view) {
fun bind(chattRitem: ChattRItem) {
itemView.textView_essentials_name.text = chattRitem.itemName
}
}
So basically I figured it out. This was not the right error from LogCat. There was another set of errors from Logcat many lines above this. The error was the result list was a #Realm object. My recyclerView was asking for a non RealmClass object. So i had to make a similar object except not a RealmClass.
#RealmClass
open class ChattRItem: RealmModel {
#PrimaryKey var itemId: String = ""
var itemName: String = ""
var itemCategory: String = ""
var itemImageFileName: String = ""
var itemAudioFileName: String = ""
}
class ChattRBoxItems(val itemId: String, val itemName: String, val itemCategory: String, val itemImageFileName: String, val itemAudioFileName: String)
then I mapped the result into this new class then applied it to my recyclerView.

Categories

Resources