i am new in android, i am working on an app that retrieve data from server using retrofit and kodein and MvvM in kotlin
i set a Navigation drawer in my app and the purpose is that when i click on the item of navigation drawer
new activity opens and in this activity i want show recyclerview
but when new activty opens recyclerview cant set listitem on recycler
i debug my code in my repositories and viewmodel class and i see that they received data
i debug my code in new activty and i see that viewmodel cant receive those data and set a invalid icon next to my code in viewmodel.observe
this is my repository class:
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
val dpData:MutableLiveData<List<DigitalProduct>> = MutableLiveData<List<DigitalProduct>>()
val apiClient = ApiClient()
val call:Call<List<DigitalProduct>> = apiClient.getClient().create(ApiService::class.java).getdigitalproduct()
call.enqueue(object : Callback<List<DigitalProduct>>{
override fun onResponse(
call: Call<List<DigitalProduct>>,
response: Response<List<DigitalProduct>>
) {
dpData.value = response.body()
}
override fun onFailure(call: Call<List<DigitalProduct>>, t: Throwable) {
dpData.value = null
}
})
return dpData
}
this is for ViewModel
var repoDigitalProduct: LiveData<List<DigitalProduct>> = repositorys.getdigitalproduct()
fun getdigitalproduct(): LiveData<List<DigitalProduct>>{
return repoDigitalProduct
}
this is for new activity:
private fun getDigitalProduct() {
viewModel.getdigitalproduct().observe(this, Observer {
digipro.addAll(it)
})
this is digipro:
var digipro: ArrayList<DigitalProduct> = ArrayList()
and this i use this code in oncreate method in new activity:
viewModel = ViewModelProviders.of(this, factory).get(AllViewModel::class.java)
getDigitalProduct()
setdigitalProductRecycler()
i use viewmodelprovider.of code in Mainactivity too
this is for setdigitalProductRecycler:
private fun setdigitalProductRecycler() {
val digiproRecycler = digital_product_recycler
digiproRecycler.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
digiproRecycler.adapter = DigitalProductAdapter(digipro)
}
my codes works in Mainactivity but when i try it in new activity.........
what should i do?
The best way to solve this issue is create your viewmodel in activity or you can use shared viewmodel. So your viewmodel will retain as your activity retain
https://stackoverflow.com/a/52611554/8868638
i put repositorie and viewmodel in companion object then i called viewmodel of MainActivity in that activity
this is for MainActivty:
companion object{
val repositorys = AllRepositorys()
var viewModel: AllViewModel = AllViewModel(repositorys)
}
this is for newActivity:
lateinit var viewModel: AllViewModel
and i put this in oncreate() Method in new activity:
viewModel = MainActivity.viewModel
UPDATE
Now I handle this issue with creating a viewmodel for every fragment or activity. Because, ViewModel is designed to store and manage UI-related data in a lifecycle of Activity or Fragment
Related
I have implemented a method that launches the BottomSheetDialog but I am facing this error
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:907)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
at android.app.Dialog.show(Dialog.java:342)
at com.example.moveapplication.adapter.PostAdapter.openBottomSheet(PostAdapter.kt:136)
at com.example.moveapplication.adapter.PostAdapter.onBindViewHolder$lambda-2(PostAdapter.kt:53)
at com.example.moveapplication.adapter.PostAdapter.$r8$lambda$Ya4NMbtCP1ASbfWIkCscOWrPyOw(Unknown Source:0)
at com.example.moveapplication.adapter.PostAdapter$$ExternalSyntheticLambda2.onClick(Unknown Source:4)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27336)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Here is method called after an item click
private fun openBottomSheet(publisher: String) {
val inflater = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.bottom_sheet_dialog, null)
val uImage = view.findViewById<ImageView>(R.id.sheetIvProfileImage)
val uNumber = view.findViewById<TextView>(R.id.sheetTvOwnerNumber)
val uEmail = view.findViewById<TextView>(R.id.tvOwnerEmail)
val uAbout = view.findViewById<TextView>(R.id.tvOwnerAbout)
val uTitle = view.findViewById<TextView>(R.id.sheetTvOwnerTitle)
val ownersRef = FirebaseFirestore.getInstance().collection("owners").document(publisher)
ownersRef.get().addOnSuccessListener { document ->
val ownerModel = document.toObject(Owner::class.java)
Glide.with(mContext)
.load(ownerModel!!.profileImage)
.into(uImage)
uTitle.text = ownerModel.username
uNumber.text = ownerModel.phoneNumber
uEmail.text = ownerModel.email
uAbout.text = ownerModel.about
}
val bottomSheetDialog = Dialog(mContext)
bottomSheetDialog.apply {
setContentView(view)
setCancelable(true)
window?.setLayout(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
window?.setGravity(Gravity.BOTTOM)
show()
}
}
I am not familiar with another way of doing inflating the bottomsheetdialog. Using the method was a trial to see if it could work.
on my onBindViewHolder the item responsible for click i've implemented it this way
holder.publisher.setOnClickListener {
openBottomSheet(post.publisher)
}
Create a interface
interface RecyclerClickListeners {
fun onRecyclerViewItemClick(view: View, model:Model) }
Add interface to RecyclerAdapter and Fragment
Fragment:
class TestFragment :
Fragment(),
RecyclerClickListeners {
private val recyclerAdapter = TestRecyclerAdapter(arrayListOf(),this)... }
Recycler Adapter:
class TestRecyclerAdapter(private val modelList: ArrayList<Model>, val listener: RecyclerClickListeners) ...
Send your model in onBindViewHolder to Fragment
holder.binding.root.setOnClickListener {
listener.onRecyclerViewItemClick(it, modelList[position])
}
In Fragment call or create bottomsheetdialog
override fun onRecyclerViewItemClick(view: View, model: Model) { openBottonSheetFragment(model) }
Now you can work with ownerModel in Bottom Sheet Fragment
private void showBottomSheetDialog() {
final BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog);
...
bottomSheetDialog.show();
}
call above function on recycler item click
To do that you can easily use material design bottom sheet. first you need to add material dependency to your build.gradle:
dependencies {
// ...
implementation 'com.google.android.material:material:<version>'
// ...
}
then you should create a kotlin file/class and extend BottomSheetDialogFragment()
class ModalBottomSheet : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.modal_bottom_sheet_content, container, false)
}
you can also set different styles to your custom bottomsheet
you can see the basic usage below:
class MainActivity : AppCompatActivity() {
...
val modalBottomSheet = ModalBottomSheet()
modalBottomSheet.show(supportFragmentManager, TAG)
...
}
Update
you can create an interface and use it in your adapter constructor to return the callback to the parent(where you create that adapter)
interface PostAdapterCallback{
onPublisherClicked(publisher : String)
//or
onOwnerClicked(owner: Owner)
}
class PostAdapter(private val publisherList: List<String>, val callback:PostAdapterCallback){}
now you can take your model inside adapter and pass the Owner model as callback or you can pass your publisher to your Activity and call your FirebaseFirestore.getInstance() there.
in your activity:
override fun onOwnerClicked(owner : Owner){
//you can call you bottomsheet here and pass you model as a bundle if you need
}
I'm looking for a list of movies in an API, the project is in MVVM and the search is being done perfectly. However, when opening the app, the user has to exit and open it again for the list to display the results. How to solve this?
In onStart I use Observe
override fun onStart() {
super.onStart()
Log.d("TESTE", "onStart")
viewModel.movieList.observe(this, Observer {
Log.d("TAG", "onCreate: $it")
for (i in it.results){
list.add(i)
}
if (page < 66){
page ++
}
})
In onResume
override fun onResume() {
super.onResume()
val scope = MainScope()
adapter.setDataSet(list)
scope.launch {
viewModel.getAllMovies(page)
viewModel.getAllGenre()
}
val recyclerView : RecyclerView = findViewById(R.id.recycler_vie_movie_list)
val layoutManager = GridLayoutManager(applicationContext, 3, GridLayoutManager.VERTICAL, false)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
You are using activity callbacks incorrectly. White all of these code in onCreate (if you use Activity or use onViewCreated if it is Fragment).
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getMovieList() // realize this method in your ViewModel
// and don't forget to set data to your movieList LiveData
val recyclerView : RecyclerView = findViewById(R.id.recycler_vie_movie_list)
val layoutManager = GridLayoutManager(applicationContext, 3, GridLayoutManager.VERTICAL, false)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
viewModel.movieList.observe(this, Observer { list->
adapter.setDataSet(list)
})
Inside the method setDataSet call notifyDataSetChanged()
Good luck in your endeavors!
I am working on an app with 5 fragments, having one fragment named home fragment.
I want whenever I press the back button on any of the other fragments, it navigates to the home fragment only.
pls, suggest to me how can I do this.? the below code is provided for reference
val expensefragment = expenseFragment()
val notesfragment = notesFragment()
val forumfragment = forumFragment()
val printfragment = printFragment()
val homefragment = homefragment()
setCurrentFragment(homefragment)
val bottomNavigationview = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
bottomNavigationview.setOnNavigationItemSelectedListener { item ->
when(item.itemId) {
(R.id.expensebar)-> setCurrentFragment(expensefragment)
(R.id.notesbar)-> setCurrentFragment(notesfragment)
(R.id.printbar)-> setCurrentFragment(printfragment)
(R.id.forumbar)-> setCurrentFragment(forumfragment)
(R.id.homebar) -> setCurrentFragment(homefragment)
}
true
}
}
private fun setCurrentFragment(fragment: Fragment){
supportFragmentManager.beginTransaction().apply {
replace(R.id.frameLayout,fragment)
commit()
}
}
After replace and before commit call the following function
.addToBackStack(backStateName);
if you don't want to give name of stack , pass null or empty value. This function will create backstack as Intent does.
I have an activity that shows two fragments at the same time on tablet and one at a time on handset. Because I am making the app for both tablets and android, I have to separate the functionality of the navigation between fragments into a separate function "displaySecondFragmentOnHandset()".
On the smaller handset when a move from the first fragment to the second and the then try to go back to the first, the screen is blank.
MyActivity:
class CentralActivity : AppCompatActivity() {
val manager = supportFragmentManager
var firstFrag : FirstFrag? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
var ft: FragmentTransaction = manager.beginTransaction();
firstFrag = FirstFrag.newInstance()
ft.add(R.id.real_container, firstFrag!!, "firstFrag")
if (screenLayoutSize >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
var secondFrag = SecondFrag.newInstance()
ft.add(R.id.real_container, secondFrag, "secondFrag")
}
ft.commit()
}
fun displaySecondFragmentOnHandset (){
var secondFrag = SecondFrag.newInstance()
var ft: FragmentTransaction = manager.beginTransaction();
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
}
Then in my FirstFragemnt, if i'm on a smaller handset I do:
class FirstFragemnt : Fragment() {
private var viewModel: SharedViewModel? = null
fun goToSecondFragment(){
if (screenLayoutSize!! < Configuration.SCREENLAYOUT_SIZE_LARGE) {
viewModel!!.setMsgInCommunicator(collection)
var centralActivity: CentralActivity = activity as CentralActivity
centralActivity.displaySecondFragmentOnHandset()
}
}
The problem is when I press the back button to go to the first fragment, The screen is blank.
in CentralActivity replace
ft!!.replace(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
with
ft!!.add(R.id.real_container, secondFrag).addToBackStack("secondFrag").commit()
The solution was to put the fun goToSecondFragment() inside of override fun onViewCreated
I tried to send data from adapter A to Activity A.
In adapter A I used Intent.putExtra() and it's success.
And in Activity A I used getStringExtra() to get the data.
When I clicked an item , the getStringExtra() returns null value. But, when I don't close my app and click the same item again, I got the value.
This is Adapter A :
class AdapterA (private val daftarMeja: ArrayList<DaftarMeja.Meja>, private val clickListener: (DaftarMeja.Meja) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
companion object {
const val ID_MEJA = "idMeja"
const val STATUS_MEJA = "statusMeja"
}
override fun getItemCount(): Int {
return daftarMeja.size
}
fun updateData(dataBaru: ArrayList<DaftarMeja.Meja>) {
daftarMeja.clear()
daftarMeja.addAll(dataBaru)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.item_meja,parent,false)
return PartViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as PartViewHolder).bind(daftarMeja[position], clickListener)
}
inner class PartViewHolder (itemView: View): RecyclerView.ViewHolder(itemView){
fun bind(meja: DaftarMeja.Meja, clicklistener: (DaftarMeja.Meja) -> Unit){
itemView.tv_table_name.text = meja.nama
if(meja.status){
itemView.container_table.setBackgroundResource(R.color.colorTableAvailable)
itemView.tv_table_name.setTextColor(Color.BLACK)
}
else {
itemView.container_table.setBackgroundResource(R.drawable.gradient)
itemView.tv_table_name.setTextColor(Color.WHITE)
}
itemView.setOnClickListener {
val intent = Intent(itemView.context, ActivityA::class.java)
intent.putExtra(ID_MEJA, meja.id)
intent.putExtra(STATUS_MEJA, meja.status)
itemView.context.startActivity(intent)
clicklistener(meja)
}
}
}
}
To get the data in Activity A :
val idMeja = intent.getStringExtra(MejaAdapter.ID_MEJA)
When the item clicked :
private fun mejaItemClicked() {
val intent = Intent(this, ActivityB::class.java)
startActivity(intent)
}
I don't know where is the problem. Please help me to solve this
1)In Activity onCreate() , this is the right mode to test Bundle
Bundle extras = getIntent().getExtras();
if (extras != null)
{
if (extras.containsKey("YOURNAMEPARAM"))
{....your assign}
}
2)Wend lanch a intent FIRST you put in extra of intent the value
val intent = Intent(this, ActivityB::class.java)
intent.putExtra("YOURNAMEPARAM",yourvalue);
startActivity(intent)
3)Make sure that the type of variable you are passing is consistent and use the "PUT" and the "GET"corresponding to the variable.
4)Make sure the activity is not already open, otherwise the onCreate () method will not be called and your getextra will not be re-evaluated.