Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get an activity's context from view model class

I based my code on an example I found that uses Android Architecture Components and data binding. This is a new way for me, and the way it is coded makes it hard to properly open a new activity with the information of the post that was clicked.

This is the adapter of the posts

class PostListAdapter : RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
    private lateinit var posts: List<Post>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
        val binding: ItemPostBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_post,
            parent, false
        )

        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
        holder.bind(posts[position])
    }

    override fun getItemCount(): Int {
        return if (::posts.isInitialized) posts.size else 0
    }

    fun updatePostList(posts: List<Post>) {
        this.posts = posts
        notifyDataSetChanged()
    }

    inner class ViewHolder(private val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root) {
        private val viewModel = PostViewModel()

        fun bind(post: Post) {
            viewModel.bind(post)
            binding.viewModel = viewModel
        }
    }
}

The bind method comes from within the view model class:

class PostViewModel : BaseViewModel() {
    private val image = MutableLiveData<String>()
    private val title = MutableLiveData<String>()
    private val body = MutableLiveData<String>()

    fun bind(post: Post) {
        image.value = post.image
        title.value = post.title
        body.value = post.body
    }

    fun getImage(): MutableLiveData<String> {
        return image
    }

    fun getTitle(): MutableLiveData<String> {
        return title
    }

    fun getBody(): MutableLiveData<String> {
        return body
    }

    fun onClickPost() {
        // Initialize new activity from here, perhaps?
    }
}

And in the layout XML, setting on an onClick attribute

android:onClick="@{() -> viewModel.onClickPost()}"

pointing to this onClickPost method does work but I can't initialize the Intent from there. I tried many ways to acquire the MainActivitiy's context, without success, such as

val intent = Intent(MainActivity::getApplicationContext, PostDetailActivity::class.java)

But it displays an error on time.

like image 446
fermoga Avatar asked Aug 27 '18 03:08

fermoga


People also ask

How do you pass context in view model?

you can access the application context from getApplication(). getApplicationContext() from within the ViewModel. This is what you need to access resources, preferences, etc.. The ViewModel class does not have the getApplication method.

How do you get the context of an application class?

It is used to return the Context which is linked to the Application which holds all activities running inside it. When we call a method or a constructor, we often have to pass a Context and often we use “this” to pass the activity Context or “getApplicationContext” to pass the application Context.


1 Answers

Try: android:onClick="@{(view) -> viewModel.onClickPost(view)}"

Also change onClickPost to take in a View. Then you can use the view.getContext() method on the view to get access to the Context stored in that view.

However, since ViewModels shouldn't reference a view or any other class that holds an Activity's context, it's quite inappropriate to place your logic for starting an Activity in the ViewModel. You should definitely consider a separate place to do so.

Personally, for my code, if it's a simple startActivity without any extra baggage, I create a separate class that holds a static method. Through databinding, I'll import that class and use it in the onClick to start a new Activity using the method I said above.

An example of this:

public class ActivityHandler{        
    public static void showNextActivity(View view, ViewModel viewModel){
        Intent intent = new Intent(); //Create your intent and add extras if needed
        view.getContext().startActivity(intent);
    }
}

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="whatever.you.want.ActivityHandler" />
        <variable name="viewmodel" type="whatever.you.want.here.too.ViewModel" />
    </data>

    <Button
        //Regular layout properties
        android:onClick="@{(view) -> ActivityHandler.showNextActivity(view, viewmodel)}"
        />
</layout>

Look at Listener Bindings here: https://developer.android.com/topic/libraries/data-binding/expressions#listener_bindings

However, depending on the amount of data necessary, you might want to place your startActivity code in other classes that best fits your app's design.

like image 179
Jackey Avatar answered Sep 28 '22 17:09

Jackey