Can I Inject activity's viewmodel and use It inside Dialog Fragment - android-studio

I am trying to Inject activity's view model and I want use it inside Dialog Fragment, how to inject it with Kodein? and use the view model which I've inject before in other activity or fragments.
I've try tutorial from medium here is the tutorial
https://proandroiddev.com/android-viewmodel-dependency-injection-with-kodein-249f80f083c9
I am trying to access injection result from activity's view model
but when I try access some object inside the viewModel from Dialog Fragment the value is null not the same with activity's viewModel
// This is from Activity
private val viewModelFactory: TriplogisticViewModelFactory by instance()
private val viewModel: TriplogisticViewModel by lazy {
ViewModelProviders
.of(this#ContactDetailBottomSheetDialogFragment, viewModelFactory)
.get(TriplogisticViewModel::class.java)
}
Log.e("VIEWMODEL_ACTIVITY", viewModel.mode.get().toString) // result is SENDER_MODE
I am expecting when I access some object inside the dialog fragment's viewModel, I got same value as activity's viewModel
// This is from Dialog Fragment
private val viewModelFactory: TriplogisticViewModelFactory by instance()
private val viewModel: TriplogisticViewModel by lazy {
ViewModelProviders
.of(this#ContactDetailBottomSheetDialogFragment, viewModelFactory)
.get(TriplogisticViewModel::class.java)
}
I want same result as activity's viewModel object but I got null result
Log.e("VIEWMODEL_FRAGMENT", viewModel.mode.get().toString) // result is null

You have to use shared ViewModel. Use activity owner for instantiating the ViewModel. Looks like:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
For details click here

Related

Update method of ViewModel class is not working

I am making this basic Notes App named Scribble
I wrote ViewModel class and all of its methods that is insert and delete are working fine but for some reason update method is not working, Its just not updating the passed note object
Please find code below :
First of all this is my Note i.e. Entity Class :
#Entity(tableName = "NotesTable")
data class Note constructor(
#ColumnInfo(name = "title") var noteTitle: String,
#ColumnInfo(name = "description") var noteDescription: String,
#ColumnInfo(name = "timeStamp") var timeStamp: String
):java.io.Serializable {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id") var id : Int = 0
}
NoteViewModel Class :
class NoteViewModel (application: Application) : AndroidViewModel (application){
val allNotes: LiveData<List<Note>>
val repository: NoteRepository
init {
val dao = NoteDatabase.getDatabase(application).getNotesDao()
repository = NoteRepository(dao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) {
viewModelScope.launch(Dispatchers.IO){
repository.insertNote(note)
}
}
fun deleteNote(note: Note) = viewModelScope.launch(Dispatchers.IO){repository.deleteNote(note)}
fun updateNote(note: Note) = viewModelScope.launch(Dispatchers.IO){repository.updateNote(note)}
Repository class :
class NoteRepository (private val noteDao: NoteDao) {
val allNotes : LiveData<List<Note>> = noteDao.getAllNotes()
suspend fun insertNote(note : Note){
noteDao.insert(note)
}
suspend fun deleteNote(note : Note){
noteDao.delete(note)
}
suspend fun updateNote(note : Note){
noteDao.update(note)
}
DAO class :
#Dao
public interface NoteDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(note: Note)
#Update
fun update(note: Note)
#Delete
fun delete(note: Note)
#Query("Select * from NotesTable order by id ASC")
fun getAllNotes() : LiveData<List<Note>>
Now this is my EditNote Activity on which I come after clicking on a note card in my recycler view I am saving note when user presses back button after editing the note thus called updateNote method inside onDestroy :
class EditNoteActivity : AppCompatActivity() {
lateinit var titleOfNote : EditText
lateinit var bodyOfNote : EditText
lateinit var noteToUpdate: Note
lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_note)
noteViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(application))[NoteViewModel::class.java]
titleOfNote = findViewById(R.id.titleEt)
bodyOfNote = findViewById(R.id.bodyEt)
noteToUpdate = intent.getSerializableExtra("noteObject") as Note
titleOfNote.setText(noteToUpdate.noteTitle)
bodyOfNote.setText(noteToUpdate.noteDescription)
}
fun updateNote(){
Log.d("AppLogs", "updateNote: called")
noteToUpdate.noteTitle = titleOfNote.text.toString()
noteToUpdate.noteDescription = bodyOfNote.text.toString()
val current = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy \nHH:mm")
noteToUpdate.timeStamp = current.format(formatter)
noteViewModel.updateNote(noteToUpdate)
}
override fun onDestroy() {
super.onDestroy()
Log.d("AppLogs", "onDestroy(updateNote): called")
updateNote()
}
I've used literally the same logic in AddNoteActivity and it works, it saves my note when i press back button and recycler view shows it perfectly
AddNoteActivity class :
class AddNoteActivity : AppCompatActivity() {
lateinit var noteTitle : EditText
lateinit var noteBody : EditText
lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_note)
noteTitle = findViewById(R.id.noteTitleEt)
noteBody = findViewById(R.id.noteBodyEt)
noteViewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[NoteViewModel::class.java]
}
fun saveNote(){
Log.d("AppLogs", "saveNote: called")
var titleOfNote : String = noteTitle.text.toString()
var bodyOfNote : String = noteBody.text.toString()
val current = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy \nHH:mm")
val timeStamp = current.format(formatter)
var note = Note(titleOfNote,bodyOfNote,timeStamp)
noteViewModel.insertNote(note)
}
override fun onStop() {
super.onStop()
Log.d("AppLogs", "onStop: called !")
saveNote()
}
}
means insertNote method from NoteViewModel works fine, similarly deleteNote also works fine, it deletes the note when I press delete button its just something wrong with updateNote method that I am not able to figure out
OTHER CLASSES ::::
NoteViewAdapter Class :
class NoteViewAdapter( allNotes : List<Note>, context: Context, clickListeners: ClickListeners)
: RecyclerView.Adapter<NoteViewAdapter.NoteViewHolder>() {
private val clickListeners : ClickListeners
private val context : Context
private var allNotes : List<Note>
init {
this.clickListeners = clickListeners
this.context = context
this.allNotes = allNotes
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: NoteViewAdapter.NoteViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.note_row,parent,false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.noteTitleTV.text = allNotes[position].noteTitle
holder.noteBodyTV.text = allNotes[position].noteDescription
holder.timeStampTV.text = allNotes[position].timeStamp
holder.deleteButton.visibility = View.INVISIBLE
holder.deleteButton.setOnClickListener {
clickListeners.onDeleteButtonClick(allNotes[position])
}
holder.itemView.setOnClickListener{
clickListeners.onNoteClick(allNotes[position])
}
holder.itemView.setOnLongClickListener{
holder.deleteButton.visibility = View.VISIBLE
val timer = Timer("schedule", true);
timer.schedule(2000) {
holder.deleteButton.visibility = View.INVISIBLE
}
return#setOnLongClickListener true
}
}
override fun getItemCount(): Int {
return allNotes.size
}
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val noteTitleTV: TextView = itemView.findViewById(R.id.titleTV)
val noteBodyTV: TextView = itemView.findViewById(R.id.bodyTV)
val deleteButton: FloatingActionButton = itemView.findViewById(R.id.deleteButton)
val timeStampTV : TextView = itemView.findViewById(R.id.timeStamp)
}
}
interface ClickListeners {
fun onNoteClick(note: Note)
fun onDeleteButtonClick(note: Note)
}
MainActivity class :
class MainActivity : AppCompatActivity(), ClickListeners {
lateinit var recyclerView : RecyclerView
lateinit var addButton : FloatingActionButton
lateinit var viewModel : NoteViewModel
lateinit var adapter : NoteViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.NotesRV)
addButton = findViewById(R.id.addButton)
viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[NoteViewModel::class.java]
viewModel.allNotes.observe(this, Observer {list ->
adapter = NoteViewAdapter(list ,this, this)
recyclerView.adapter = adapter
})
recyclerView.layoutManager = GridLayoutManager(this,2,GridLayoutManager.VERTICAL,false)
addButton.setOnClickListener{
val intent : Intent = Intent(this,AddNoteActivity::class.java)
startActivity(intent)
}
}
override fun onNoteClick(note: Note) {
val intent : Intent = Intent(this,EditNoteActivity::class.java)
intent.putExtra("noteObject",note)
startActivity(intent)
}
override fun onDeleteButtonClick(note: Note) {
viewModel.deleteNote(note)
}
Here I am passing note object with intent Extras because I implemented java.io.Serializable in Note class
When I click on note it opens edit note activity then I change the previous text set there, but when i press back button it doesnt update note
I used debug logs and found that both onDestroy and updateNote are getting called but still it is note updating my note
Also these lines from EditNoteActivity.kt are setting previous entered text in note after clicking on note card
titleOfNote.setText(noteToUpdate.noteTitle)
bodyOfNote.setText(noteToUpdate.noteDescription)
but for some reason this line isn't working
noteViewModel.updateNote(noteToUpdate)

Firebase Realtime Database images are not showing up in fragment kotlin class

I'm trying to show images from my Firebase Realtime Database storage. I've done this before with a previous version of my app, but the difference is how I implemented it. My adapter and arraylist class are exactly the same, but instead of using an activity I switched to using fragments.
What I essentially did was copy my old work and make the appropriate changes so I wouldn't run into errors, but unfortunately I ran into some. My images from Firebase are not showing up at all and I'm not sure what is the problem.
Adapter Class
class AbstractAdapter(private val mContext: Context, private val abstractList: ArrayList<Abstract>) : RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
}
override fun getItemCount(): Int {
return abstractList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
}
companion object {
private const val Tag = "RecyclerView"
}
}
Data class
class Abstract {
var abstract: String? = null
constructor() {}
constructor(abstract: String?) {
this.abstract = abstract
}
}
Fragment in which images will be shown
class AbstractWallpapers: Fragment(), PurchasesUpdatedListener {
private lateinit var subscribeAbstract: Button
private var billingClient: BillingClient? = null
lateinit var recyclerView: RecyclerView
lateinit var abstractlist: ArrayList<Abstract>
private var recyclerAdapterAbstract: AbstractAdapter? = null
private var myRef3: DatabaseReference? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_abstract_wallpaper, container, false)
recyclerView = requireView().findViewById(R.id.abstract_recyclerView)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = view.findViewById(R.id.abstract_recyclerView)
val layoutManager = LinearLayoutManager(requireActivity())
recyclerView.layoutManager = layoutManager
recyclerView.setHasFixedSize(true)
myRef3 = FirebaseDatabase.getInstance().reference
abstractlist = ArrayList()
ClearAll()
GetDataFromFirebase()
subscribeAbstract = view.findViewById(R.id.abstract_subscribe_btn)
subscribeAbstract.setOnClickListener {
subscribeAbstract()
}
// Establish connection to billing client
//check subscription status from google play store cache
//to check if item is already Subscribed or subscription is not renewed and cancelled
billingClient = BillingClient.newBuilder(requireActivity()).enablePendingPurchases().setListener(this).build()
billingClient!!.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val queryPurchase = billingClient!!.queryPurchases(BillingClient.SkuType.SUBS)
val queryPurchases = queryPurchase.purchasesList
if (queryPurchases != null && queryPurchases.size > 0) {
handlePurchases(queryPurchases)
} else {
saveSubscribeValueToPref(false)
}
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(requireActivity(), "Service Disconnected", Toast.LENGTH_SHORT).show()
}
})
//item subscribed
if (subscribeValueFromPref) {
subscribeAbstract.visibility = View.GONE
} else {
subscribeAbstract.visibility = View.VISIBLE
}
}
// Code related to Firebase
#SuppressLint("NotifyDataSetChanged")
private fun GetDataFromFirebase() {
val query: Query = myRef3!!.child("Abstract")
query.addListenerForSingleValueEvent(object : ValueEventListener {
#SuppressLint("NotifyDataSetChanged")
override fun onDataChange(snapshot: DataSnapshot) {
for (dataSnapshot: DataSnapshot in snapshot.children) {
val abstract = Abstract()
abstract.abstract = dataSnapshot.child("abstract").value.toString()
abstractlist.add(abstract)
}
recyclerAdapterAbstract = abstractlist.let { AbstractAdapter(requireActivity(), it) }
recyclerView.adapter = recyclerAdapterAbstract
recyclerAdapterAbstract!!.notifyDataSetChanged()
}
override fun onCancelled(error: DatabaseError) {}
})
if (recyclerAdapterAbstract != null) recyclerAdapterAbstract!!.notifyDataSetChanged()
}
private fun ClearAll() {
abstractlist.clear()
abstractlist = ArrayList()
}
I fixed my problem. It turns out my rules in Firebase Realtime Database rules for read were set to false instead. My code works perfect. It was only a stupid error on my part.

Jetpack Compose Material date range picker [duplicate]

I want to open datePicker dialog on a button click in Jetpack compose.
For that, I am using the below code inside the button's onClick action.
val context = LocalContext.current
Button(onClick = {
(context as AppCompatActivity).let {
val picker = MaterialDatePicker.Builder.datePicker().build()
picker.show(it.supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
// some code
}
}
})
If I am using the ComponentActivity, supportFragmentManager is not supported.
Is it fine to extend the activity from AppCompatActivity?
Or is there any other way, I can get the solution if the above-mentioned solution is not correct?
You can use the AppCompatActivity since it extends FragmentActivity which extends ComponentActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val activity = LocalContext.current as AppCompatActivity
Button(onClick={ showDatePicker(activity)}){
Text("Picker")
}
}
}
}
fun showDatePicker(activity: AppCompatActivity){
val picker = MaterialDatePicker.Builder.datePicker().build()
activity?.let {
picker.show(it.supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
}
}
}
Note: it requires at least the AppCompat 1.3.0 version.

How to pass string from activity to another activity in kotlin

I am trying to pass over a text to my second activity but it does not seem to be working correctly.
For my main activity i have a a textview and the text says 'Cat'. When the image on the first activity is pressed on, I want "Cat" to be passed over to the second activity. The second activity's textview should also display "Cat", however when I do this, it will show up as "Info(name=Cat)" rather then just "Cat".
I am not sure where i went wrong.
This is my main activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val image = findViewById<ImageView>(R.id.imageView)
val text = findViewById<TextView>(R.id.textView)
image.setOnClickListener {
val intent= Intent(this, MainActivity2::class.java).apply {
putExtra("name", Info("Cat"))
}
startActivity(intent)
}
}
}
My parcelable class
#Parcelize
data class Info(val name : String) : Parcelable {
}
This is my second activity
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val info = intent.getParcelableExtra<Info>("name")
val name = findViewById<TextView>(R.id.textView2)
name.text = info.toString()
}
}
You're converting the whole Info data class to its string representation. You only want the name property from the data class.
name.text = info.name

Kotlin with Anko unknown resource and null reference

I Want to get Data from https://www.thesportsdb.com/api/v1/json/1/eventspastleague.php?id=4328
But I have error
java.lang.IllegalArgumentException: Parameter specified as non-null is
null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull,
parameter data at
com.yutra.jadwalbola.last_match.LastMatchFragment.showTeamList(Unknown
Source:21) at
com.yutra.jadwalbola.last_match.LastMatchPresenter$getTeamList$1$1.invoke(LastMatchPresenter.kt:21)
at
com.yutra.jadwalbola.last_match.LastMatchPresenter$getTeamList$1$1.invoke(LastMatchPresenter.kt:8)
this is my LastMatchDBApi
object LastMatchDBApi {
fun getMatch(): String {
return Uri.parse(BuildConfig.BASE_URL).buildUpon()
.appendPath("api")
.appendPath("v1")
.appendPath("json")
.appendPath(BuildConfig.TSDB_API_KEY)
.appendPath("eventspastleague.php")
.appendQueryParameter("id", "4328")
.build()
.toString()
}
}
this is my serialized
data class LastMatch(
#SerializedName("strEvent")
var eventName: String? = null
)
this is my presenter
class LastMatchPresenter(private val view: MainView,
private val apiRepository: ApiRepository,
private val gson: Gson) {
fun getTeamList() {
view.showLoading()
doAsync {
val data = gson.fromJson(apiRepository
.doRequest(LastMatchDBApi.getMatch()),
LastMatchResponse::class.java
)
uiThread {
view.hideLoading()
view.showTeamList(data.lastMatch)
}
}
}
}
this is my response
data class LastMatchResponse(
val lastMatch: List<LastMatch>
)
this is my fragment activity
class LastMatchFragment : Fragment(), MainView {
private lateinit var progressBar: ProgressBar
private lateinit var swipeRefresh: SwipeRefreshLayout
private var lastMatch: MutableList<LastMatch> = mutableListOf()
private lateinit var adapter: MainAdapter
private lateinit var listTeam: RecyclerView
private var key : String = "4328"
companion object {
fun newInstance(): LastMatchFragment {
return LastMatchFragment()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = UI {
linearLayout {
lparams (width = matchParent, height = wrapContent)
orientation = LinearLayout.VERTICAL
topPadding = dip(16)
leftPadding = dip(16)
rightPadding = dip(16)
// textView {
// text = "test image"
// }
relativeLayout{
lparams (width = matchParent, height = wrapContent)
listTeam = recyclerView {
lparams (width = matchParent, height = wrapContent)
layoutManager = LinearLayoutManager(ctx)
}
progressBar = progressBar {
}.lparams{
centerHorizontally()
}
}
}
}.view
adapter = MainAdapter(lastMatch)
listTeam.adapter = adapter
val request = ApiRepository()
val gson = Gson()
var presenter = LastMatchPresenter(this, request, gson)
presenter.getTeamList()
return view
}
override fun showLoading() {
progressBar.visible()
}
override fun hideLoading() {
progressBar.invisible()
}
override fun showTeamList(data: List<LastMatch>) {
lastMatch.clear()
lastMatch.addAll(data)
adapter.notifyDataSetChanged()
}
}
IF anyone have sourcode to get this data please help me
In below code you are passing list as null from LastMatchPresenter class
view.showTeamList(data.lastMatch)
By default all variables and parameters in Kotlin are non-null. If you want to pass null parameter to the method you should add ? to it's type, for example:
override fun showTeamList(data: List<LastMatch>?) {
lastMatch.clear()
lastMatch.addAll(data)
adapter.notifyDataSetChanged()
}

Resources