Can ViewModel observe its MutableLiveData property? - kotlin

I'm trying to use ViewModels with LiveData and MutableLiveData in Android for the first time. I have not read all documentation yet.
My question:
How can I call fun applyFilters() when val filterByName = MutableLiveData<String>() was changed from outside (from a Fragment).
In .NET MVVM I would just add that call in the setter for filterByName property. But how to I do it in with MutableLiveData?
I think I should use MediatorLiveData, but this documentation does not explain much.
I was thinking about using the observer inside a viewmodel (so it could observe itself), but then I would have to use AndroidViewModel instead of ViewModel, to get lifecycleOwner which is required to observe MutableLiveData...
My code:
In my AssignUhfTagToInventoryItemViewModel() : ViewModel() (which is shared between few fragments) I have properties like this:
/* This is filled only once in SomeViewModel.init() function, always contains all data */
var _items = ArrayList<InventoryItemDto?>()
/* This is filled from _items, contains filtered data */
val items = MutableLiveData<ArrayList<InventoryItemDto?>>().apply { value = ArrayList() }
val filterByName = MutableLiveData<String>().apply { value = "" }
And a function like this:
fun applyFilters(){
Log.d(TAG, "ViewModel apply filters called. Current name filter: ${filterByName.value}")
items.value?.clear()
val nameFilterLowercase = filterByName.value.toString().lowercase()
if (!filterByName.value.isNullOrEmpty()) {
for (item in _items) {
val itemNameLowercase = item?.name?.lowercase()
if (itemNameLowercase?.contains(nameFilterLowercase) == true)
items.value?.add(item)
}
Log.d(TAG, "Filter is on, data cached : ${_items.count()} rows")
Log.d(TAG, " data displayed: ${items.value?.count()} rows")
}
else {
items.value?.addAll(_items)
Log.d(TAG, "Filter is off, data cached : ${_items.count()} rows")
Log.d(TAG, " data displayed: ${items.value?.count()} rows")
}
}
And this is part of my Fragment where I use that viewmodel:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentInventoryItemSelectBinding.bind(view)
val vm = ViewModelProvider(requireActivity())[AssignUhfTagToInventoryItemViewModel::class.java]
// old school ArrayAdapter
val listAdapter = InventoryItemViewAdapter(this.context, vm.items.value)
binding.lvItems.adapter = listAdapter // old school ListView
binding.tvFilterByName.addTextChangedListener(AfterTextChangedWatcher {
vm.filterByName.postValue(it)
/*
IN MY FRAGMENT I WANT TO AVOID FUNCTION CALL BELOW
*/
vm.applyFilters()
listAdapter.notifyDataSetInvalidated()
Log.d(TAG, "afterTextChanged: $it")
})
vm.items.observe(requireActivity()){
listAdapter.notifyDataSetInvalidated()
}
}

Technically? I guess you can but you can use Transformations.switchMap to take input from one LiveData and apply them to the source from another.
var _items = MutableLiveData(ArrayList<InventoryItemDto?>())
val filterByName = MutableLiveData<String>().apply { value = "" }
val items = Transformations.switchMap(filterByName) { filter ->
val nameFilterLowercase = filter.lowercase()
_items.map { mappedItems ->
mappedItems.filter { listItem ->
listItem.contains(nameFilterLowercase)
}
}
}
vm.items.observe(viewLifecycleOwner) { items ->
// old school ArrayAdapter
val listAdapter = InventoryItemViewAdapter(this.context, items)
binding.lvItems.adapter = listAdapter // old school ListView
}

Related

How to sort recyclerview data fetched from firebase by value in mvvm architecture

I am trying to sort datas which are fetched from firebase realtime database according to the value of a child using MVVM architecture the daabase reference is created in a repository
GroupNoticeRepository
class GroupNoticeRepository(private var groupSelected: String) {
val auth = Firebase.auth
val user = auth.currentUser!!.uid
private val scheduleReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("group-notice").child(groupSelected)
#Volatile
private var INSTANCE: GroupNoticeRepository? = null
fun getInstance(): GroupNoticeRepository {
return INSTANCE ?: synchronized(this) {
val instance = GroupNoticeRepository(groupSelected)
INSTANCE = instance
instance
}
}
fun loadSchedules(allSchedules: MutableLiveData<List<GroupNoticeData>>) {
scheduleReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val scheduleList: List<GroupNoticeData> =
snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(GroupNoticeData::class.java)!!
}
allSchedules.postValue(scheduleList)
} catch (_: Exception) {
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
GroupNoticeFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recycler = binding.taskList
recycler.layoutManager = LinearLayoutManager(context)
recycler.setHasFixedSize(true)
adapter = GroupNoticeAdapter(_inflater)
recycler.adapter = adapter
viewModel = ViewModelProvider(this)[GroupNoticeViewModel::class.java]
viewModel.initialize(groupId)
viewModel.allSchedules.observe(viewLifecycleOwner) {
adapter!!.updateUserList(it)
}
}
GroupNoticeViewModel
class GroupNoticeViewModel : ViewModel() {
private lateinit var repository: GroupNoticeRepository
private val _allSchedules = MutableLiveData<List<GroupNoticeData>>()
val allSchedules: LiveData<List<GroupNoticeData>> = _allSchedules
fun initialize(groupSelected: String) {
repository = GroupNoticeRepository(groupSelected).getInstance()
repository.loadSchedules(_allSchedules)
}
}
`
As you can see the current structure
group-notice
-groupId(groups)
-noticeId (notices)
- taskDate
Here under group notice there are some groups and in each group there are some notices(noticeId) .
Each notice has a task date . Now I am trying to sort the notices according to the taskdate meaning the taskDate which will is closer to todays date will view first in the recycler view. Or the notice with latest taskdate given will appear first in the recycler view .
Just as hassan bazai said I followed the same concept of comparing two dates
class FirebaseDataComparator : Comparator<GroupNoticeData?> {
override fun compare(p0: GroupNoticeData?, p1: GroupNoticeData?): Int {
val dateFormat = SimpleDateFormat("dd/MM/yyyy")
val firstDate: Date = dateFormat.parse(p0?.taskdate!!) as Date
val secondDate: Date = dateFormat.parse(p1?.taskdate!!) as Date
return firstDate.compareTo(secondDate)
}
}
Here groupNoticeData is the data class I am using to populate the data in my recycler View and took two objects of them . Parsed their date format accordingly and later on compared them.
And in the recyclerViewAdapter before adding my data, I sorted them using the comparator class and added them later on. Here is the part where I had to use the comparator class.
fun updateNoticeList(notices: List<GroupNoticeData>) {
Collections.sort(notices, FirebaseDataComparator())
this.tasks.clear()
this.tasks.addAll(notices)
notifyDataSetChanged()
}

ViewModel Instance inside RecycleView KOTLIN CANNOT CREATE INSTANCE OF VIEWMODAL

Im trying to create a View model that contains alist of countries in it.
The View modal class look like this:
class Country_ViewModel(ctx:Context) :ViewModel(){
val itemSelected : MutableLiveData<Int> by lazy{
MutableLiveData<Int>()
}
val p = XmlPullParserHandler()
private var count: MutableList<Country> = p.parse(openCountriesFile(ctx))
val countryArray =MutableLiveData(count)
// This function will open the XML file and return an input stream that will be used by the Parse function
fun openCountriesFile(context: Context): InputStream? {
val assetManager: AssetManager = context.getAssets()
var `in`: InputStream? = null
try {
`in` = assetManager.open("countries.xml")
} catch (e: IOException) {
e.printStackTrace()
}
return `in`
}
// This function will loop thorough the country list and delete the entry that it got from the position
fun removeItem(position: Int) {
count.map { pos ->
if (pos.compare(count[position]) == 0) {
count.remove(pos)
return
}
}
}
The function openCountriesFile will just parse the XML file that contains the Countries and save it in the MutableLiveData object inside the ModelView.
Later I would like to use a Fragment to observe the data that is changed:
This fragment will use the Adapter that I created and populate the Fragment with the country data.
The fragment will look like that:
class frag : Fragment(){
val KEY_COUNTRY = "country"
val KEY_NAME = "name"
val KEY_FLAG = "flag"
val KEY_ANTHEM = "anthem"
val KEY_SHORT = "short"
val KEY_DETAILS = "details"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
val viewModal:Country_ViewModel by viewModels()
/*
* When creating the view we would like to do the following:
* Initiate the Adapter.
* When the adapter has been called he will look for the XML file with the country's in it.
Second one for the anthems
* */
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_frag, container, false)
val rv = v.findViewById(R.id.rvTodos) as RecyclerView
val adapter = countryAdapter(requireContext(),viewModal,this)
viewModal.itemSelected.observe(viewLifecycleOwner, Observer<Int>{
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction.apply {
replace(R.id.fragLand, fragment2)
commit()
}
})
rv.adapter = adapter
// Apply the new content into the fragment layout
val mLayoutManager = LinearLayoutManager(activity);
rv.layoutManager = mLayoutManager
return v
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.primary_menu,menu)
}
}
Theer I would observe if there was a country that has been clicked on and if so I would like to move to my other fragment to get some more details.
My adapter look like that:
class countryAdapter(
var ctx: Context, var viewModal:Country_ViewModel, var owner: LifecycleOwner
) : RecyclerView.Adapter<countryAdapter.countryViewHolder>() {
// lateinit var mListener: onItemLongClickListener
// private lateinit var mSListener: onItemClickListener
var player: MediaPlayer? =null
private lateinit var count: MutableList<Country>
/**-----------------------------------INTERFACES --------------------------------------------------*/
// interface onItemLongClickListener {
//
// fun onItemLongClick(position: Int)
// }
//
// interface onItemClickListener {
//
// fun onItemClick(position: Int): Boolean
// }
/**-----------------------------LISTENERS --------------------------------------------------------*/
// fun setOnItemLongClickListener(listener: onItemLongClickListener) {
// mListener = listener
//
// }
// fun setOnItemClickListener(listener: onItemClickListener) {
// mSListener = listener
// }
/**-----------------------------INNER CLASS--------------------------------------------------------*/
inner class countryViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val counrtyName = itemView.findViewById<TextView>(R.id.countryName)
val populationNum = itemView.findViewById<TextView>(R.id.countryPopulation)
val imageCount = itemView.findViewById<ImageView>(R.id.imageView)
/*
* Defining the listeners in the initialization of the Row in the adapter
* */
init {
count= viewModal.countryArray.value!!
itemView.setOnLongClickListener {
viewModal.removeItem(adapterPosition)
return#setOnLongClickListener true
}
itemView.setOnClickListener{
viewModal.itemSelected.value=adapterPosition
Log.i("Hello",adapterPosition.toString())
startPlayer(adapterPosition,ctx)
}
viewModal.countryArray.observe(owner, Observer {
notifyDataSetChanged()
})
}
}
/**---------------------------------------VIEW HOLDER CREATE AND BIND ----------------------------- */
/*
* Will inflate the country XML file in the adapter and then inflate it into the parent that is
* the fragment.
* At the end it will return the inner class with all the parameters that was initiated there.
* */
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): countryAdapter.countryViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemcountry, parent, false)
val context = parent.context
val inflater = LayoutInflater.from(context)
val contactView = inflater.inflate(R.layout.itemcountry, parent, false)
return countryViewHolder(contactView)
}
/*
* The function will be responsible to get the data that was initiated at the begining in the
* inner class and change the data that is displayed in the XML file to the new data based on the
* Country that it got
* The position is parameter that is changing every time this function is called and adding all the
* Country that are in the XML into the fragment
*
* */
override fun onBindViewHolder(holder: countryViewHolder, position: Int) {
var countryName1 = holder.counrtyName
var countryPopulation1 = holder.populationNum
var imagecount = holder.imageCount
countryName1.setText(viewModal.countryArray.value?.get(position)?.name_of_country)
countryPopulation1.setText(count?.get(position)?.shorty_of_country)
count?.get(position)?.let {
country_drawable.get(it.name_of_country)?.let {
imagecount.setBackgroundResource(
it
)
}
}
}
/**-----------------------------------------Functions ------------------------------------------- */
fun startPlayer(position: Int,ctx:Context){
player?.stop()
player =
count?.get(position)
?.let { country_raw.get(it.name_of_country)?.let { MediaPlayer.create(ctx, it) } }
player?.start()
}
override fun getItemCount(): Int {
return count.size
}
}
The goal is if the user click on one of the countries in the RecyclyView (OnClickListener) then i would like to move to the second fragment.
Im having an error will creating the viewModal instance the error is:
Cannot create an instance of class com.example.Country_ViewModel
Why is that? what I'm initializing wrong?
Where should i create the instance of the ViewModal? inside the adapter or inside the fragment itself? ( is it ok to pass the viewModal instance to the adapter? or there is another way i can observe the change in the CountryArray?)
Thank you.

Kotlin RecyclerView not updating after data changes

I am using RecyclerView to display a dynamic list of data and after I call an api I need to update my RecyclerView UI but the items in my RecyclerView does not change...
Below is my how I init my RecyclerView in my Fragment:-
forwardedList.layoutManager = LinearLayoutManager(context!!, RecyclerView.VERTICAL, false)
adapter = ForwardListAdapter(SmsHelper.getForwardedSms(context!!))
forwardedList.adapter = adapter
Below is my custom RecyclerView Adapter:-
class ForwardListAdapter(val forwardedList: List<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ForwardListAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.forwarded_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ForwardListAdapter.ViewHolder, position: Int) {
holder.bindItems(forwardedList[position])
}
override fun getItemCount(): Int {
return forwardedList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(sms: SmsData) {
val simSlotText: TextView = itemView.findViewById(R.id.simSlot)
val senderText: TextView = itemView.findViewById(R.id.sender)
simSlotText.text = "[SIM ${sms.simSlot}] "
senderText.text = sms.sender
}
}
}
I am currently updating my RecyclerView from SmsHelper class as below:-
val fragments = mainActivity!!.supportFragmentManager.fragments
for (f in fragments) {
if (f.isVisible) {
if (f.javaClass.simpleName.equals("ForwardedFragment")) {
val fg = f as ForwardedFragment
fg.adapter.notifyDataSetChanged() <----- HERE
} else if (f.javaClass.simpleName.equals("FailedFragment")) {
val fg = f as FailedFragment
fg.adapter.notifyDataSetChanged()
}
}
}
As I observed, you did not really change the adapter's data but only called notifyDataSetChanged. You cannot just expect the data to be changed automatically like that since notifyDataSetChanged only:
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
You need to change the data by yourself first, then call notifyDataSetChanged.
class ForwardListAdapter(private val forwardedList: MutableList<SmsData>) : RecyclerView.Adapter<ForwardListAdapter.ViewHolder>() {
// ...
fun setData(data: List<SmsData>) {
forwardedList.run {
clear()
addAll(data)
}
}
// ...
}
Then do it like this:
adapter.run {
setData(...) // Set the new data
notifyDataSetChanged(); // notify changed
}

how to save and retrieve application info to shared preference in kotlin?

I want to hide app icon from grid view and save it.
I can save application info to shared preferences as mutable set string and get it but cant convert string to application info and show in my grid view
my app activity
private var applist: List<ApplicationInfo>? = null
private var listadaptor: ApplicationAdapter? = null
private var grid: GridView? = null
private var mSelected: ArrayList<Any> = ArrayList()
var context: Activity = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_apps)
val packageManager = getPackageManager()
LoadApplications().execute()
grid = findViewById<View>(R.id.grid) as GridView
grid!!.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE)
grid!!.adapter = listadaptor
grid!!.onItemClickListener = AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val app = applist!![position]
try {
val intent = packageManager.getLaunchIntentForPackage(app.packageName)
intent?.let { startActivity(it) }
} catch (e: Exception) {
Toast.makeText(this#appsActivity, e.message, Toast.LENGTH_LONG).show()
}
}
grid!!.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, view, position, id ->
val position1: String = (position).toString()
if (mSelected.contains(position1)) {
mSelected.remove(position1)
view.setBackgroundColor(Color.TRANSPARENT) // remove item from list
// update view (v) state here
// eg: remove highlight
} else {
mSelected.add(position1)
view.setBackgroundColor(Color.LTGRAY) // add item to list
// update view (v) state here
// eg: add highlight
}
button3.setOnClickListener(
object : View.OnClickListener {
override fun onClick(view: View?) {
val builder1: AlertDialog.Builder = AlertDialog.Builder(this#appsActivity)
builder1.setMessage("Are you sure you want to delete it ?")
builder1.setCancelable(true)
builder1.setPositiveButton(
"Yes",
DialogInterface.OnClickListener { dialog, id ->
deleteSelectedItems()
mSelected.remove(position)
listadaptor!!.notifyDataSetChanged()
val app = applist!![position]
listadaptor!!.remove(app)
})
builder1.setNegativeButton(
"No",
DialogInterface.OnClickListener { dialog, id -> dialog.cancel() })
val alert11: AlertDialog = builder1.create()
alert11.show()
}
})
true
}
}
private fun deleteSelectedItems() {
val checked: SparseBooleanArray = grid!!.getCheckedItemPositions()
if (checked != null) {
val list: List<Any> = mSelected
for (i in 0 until checked.size()) {
if (checked.valueAt(i)) {
mSelected.remove(checked.keyAt(i))
}
}
}
}
private fun checkForLaunchIntent(list: List<ApplicationInfo>): List<ApplicationInfo> {
val applist = ArrayList<ApplicationInfo>()
for (info in list) {
try {
if (null != packageManager!!.getLaunchIntentForPackage(info.packageName)) {
applist.add(info)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Collections.sort(applist, ApplicationInfo.DisplayNameComparator(packageManager))
return applist
}
#SuppressLint("StaticFieldLeak")
private inner class LoadApplications : AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
applist = checkForLaunchIntent(packageManager!!.getInstalledApplications(
PackageManager.GET_META_DATA))
listadaptor = ApplicationAdapter(this#appsActivity,
R.layout.grid_item, applist!!)
return null
}
override fun onPostExecute(result: Void?) {
grid!!.adapter = listadaptor
super.onPostExecute(result)
}
}
items are deleted, but after re-running the application, all installed apps will be restored in gridview
You can look into my open source project LibTron which has one module library for SharedPref written in Kotlin.
To use the library follow the instruction in Project ReadMe
Example to use the library:
val applicationInfo: ApplicationInfo = sharedprefrence.Object(name = "sharedprefKey", defaultValue = null)
Or in case you want to use it without the help of the you can use GSON, Moshi, Jackson type libraries to convert to/from string to your ApplicationInfo class while saving or reading from the Sharedprefrence

LocalDate.format causes OutofBount Exception in Observer

I'm making an app in android using Kotlin, Material Design Components and the new architecture components.
I have an activity that starts a DialogFragment onCreate
The fragment has 6 Views that, via an observer, observe a different LiveDate for each and every one.
While checking all the this setup work I noticed that after 7 view switching I get
2020-05-12 20:43:19.346 4778-4778/package E/InputEventReceiver: Exception dispatching input event.
2020-05-12 20:43:19.346 4778-4778/package E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2020-05-12 20:43:19.357 4778-4778/package E/MessageQueue-JNI:
java.lang.ArrayIndexOutOfBoundsException: length=9; index=9
at android.text.Layout$HorizontalMeasurementProvider.get(Layout.java:1589)
...
I cheked the following things:
did all the setup on only one view -> still crashes
did all the setup on only one view but without using the "createDateFieldObserver" method -> still carshes
not calling the observer -> no crash
calling the observer but without the LocalDate.format -> no crash
I concluded the probleme is in the format function but I do not understand why.
The error is not pointing in that direction.
Any ideas?
Activity code
class UITestingActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_greenhouse)
val dialog = LabTimesDialogFragment()
dialog.show(supportFragmentManager, "LabTimes")
}
}
Fragment Code
class TimesDialogFragment : DialogFragment() {
companion object Companion {
private val TAG: String = "TimesDialog"
}
private val datesViewModel: TimesViewModel by activityViewModels()
private lateinit var datesViews: Map<LiveData<LocalDate>, TextInputEditText>
override fun onCreateDialog(savedInstanceState: Bundle?) : Dialog {
val viewsArray: Array<TextInputEditText>
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater
val rootView: View = inflater.inflate(R.layout.dialog_filter_times, null)
builder.setView(rootView)
.setPositiveButton(R.string.feed) { dialog, id -> closeDialog() }
.setNegativeButton(R.string.cancel) { dialog, id -> getDialog()?.cancel() }
val dialog: AlertDialog = builder.create()
val fromSampling: TextInputEditText = rootView.findViewById(R.id.from_sampling) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toSampling: TextInputEditText = rootView.findViewById(R.id.to_sampling) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val fromSending: TextInputEditText = rootView.findViewById(R.id.from_sending) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toSending: TextInputEditText = rootView.findViewById(R.id.to_sending) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val fromReceiving: TextInputEditText = rootView.findViewById(R.id.from_receiving) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toReceiving: TextInputEditText = rootView.findViewById(R.id.to_receiving) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
datesViews = mapOf(datesViewModel.fromSampling to fromSampling,
datesViewModel.toSampling to toSampling,
datesViewModel.fromSending to fromSending,
datesViewModel.toSending to toSending,
datesViewModel.fromReceiving to fromReceiving,
datesViewModel.toReceiving to toReceiving
)
for ((liveData, textView) in datesViews) {
liveData.observe(this, createDateFieldObserver(textView))
textView.setOnClickListener { v ->
Log.d(TAG, "hello"+v.id)
}
}
return dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
private fun closeDialog() {
// save dates to ViewModel
// closeDialog
TODO()
}
private fun createDateFieldObserver(tw: TextInputEditText): Observer<LocalDate> {
return Observer { date ->
Log.d(TAG, "obs"+tw.id)
tw.setText(date.format(DateTimeFormatter.ISO_DATE))
//tw.setText("hello")
}
}
}
ViewModel
class TimesViewModel : ViewModel() {
val fromSampling: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toSampling: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val fromSending: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toSending: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val fromReceiving: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toReceiving: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
}
it has been a while since I programmed for android. Everything I'm using here is new to me so if you spot an anti-pattern in this little code I would be glad to know.
Tnx
Turns out it was because of the layout.
There was no enough room in the view to display the whole date.
I don't understand why it causes indexoutofbound but the solution is to simply make the view "wrap_content" or bigger