Получить контекст действия из класса модели представления

Я основал свой код на найденном мной примере, в котором используются компоненты архитектуры Android и привязка данных. Для меня это новый способ, и то, как он закодирован, затрудняет правильное открытие нового действия с информацией о посте, по которому щелкнули.

Это переходник столбов

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
        }
    }
}

Метод bind происходит из класса модели представления:

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?
    }
}

И в XML макета, устанавливая атрибут onClick

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

указание на этот onClickPost метод действительно работает, но я не могу инициализировать Intent оттуда. Я пробовал много способов получить контекст MainActivitiy, но безуспешно, например

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

Но вовремя отображает ошибку.


person fermoga    schedule 27.08.2018    source источник
comment
ViewModel НЕ должен знать контекст или что-либо об Android. Итак, я предполагаю, что представление должно подписаться на событие или что-то, испускаемое ViewModel при вызове метода onClickPost. Однако я столкнулся с аналогичной проблемой, поэтому меня интересует правильный ответ.   -  person Eselfar    schedule 27.08.2018
comment
Попробуйте singleliveevent узор   -  person MidasLefko    schedule 27.08.2018
comment
@MidasLefko, кажется, это могло быть так, но у меня проблемы с ViewModelFactory, который не является гибким и динамическим, принимая более одного типа ViewModel.   -  person fermoga    schedule 27.08.2018
comment
@gamofe, это похоже на новый вопрос ...   -  person MidasLefko    schedule 27.08.2018
comment
Он умирает. Я создал новый заголовок stackoverflow.com/questions/52033403/   -  person fermoga    schedule 27.08.2018
comment
@MidasLefko Вы должны написать ответ, так как это кажется правильным подходом.   -  person Eselfar    schedule 27.08.2018


Ответы (3)


Попробуйте: android:onClick="@{(view) -> viewModel.onClickPost(view)}"

Также измените onClickPost для просмотра. Затем вы можете использовать метод view.getContext() в представлении, чтобы получить доступ к контексту, хранящемуся в этом представлении.

Однако, поскольку ViewModels не должен ссылаться на представление или любой другой класс, содержащий контекст Activity, совершенно неуместно размещать логику для запуска Activity в ViewModel. Вам обязательно стоит подумать о отдельном месте для этого.

Лично для моего кода, если это простой startActivity без лишнего багажа, я создаю отдельный класс, содержащий статический метод. Посредством привязки данных я импортирую этот класс и использую его в onClick, чтобы начать новое действие, используя метод, который я сказал выше.

Пример этого:

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>

Посмотрите здесь привязки слушателей: https://developer.android.com/topic/libraries/data-binding/expressions#listener_bindings

Однако, в зависимости от объема необходимых данных, вы можете разместить код startActivity в других классах, которые лучше всего соответствуют дизайну вашего приложения.

person Jackey    schedule 27.08.2018
comment
Разве это не нарушает шаблон MVVM? - person Eselfar; 27.08.2018
comment
Кроме того, startActivity() недоступен в модели представления. - person fermoga; 27.08.2018
comment
@Eselfar Ага, я редактировал свой ответ, чтобы сказать именно это. - person Jackey; 27.08.2018
comment
@gamofe можно использовать startActivity() через контекст, который вы получаете из объекта View. view.getContext().startActivity() - person Jackey; 27.08.2018
comment
Можете ли вы показать кодовую базу наиболее подходящим способом? - person fermoga; 27.08.2018
comment
В моем случае, по крайней мере вначале, я хотел бы добавить дополнительные строки для заголовка, тела и дат сообщения. - person fermoga; 27.08.2018
comment
@gamofe Я отредактировал свой ответ грубым примером. Вы можете передать другие вещи, такие как ViewModel, чтобы вы могли использовать его для добавления в качестве дополнений намерения для нового Activity. - person Jackey; 27.08.2018
comment
вы не должны использовать контекст в Viewmodel, это нарушит шаблон mvvm и создаст сбои во время выполнения, все связанные с контекстом вещи должны выполняться в View - person Rohit Sharma; 27.08.2018
comment
@RohitSharma Я не говорил использовать контекст во ViewModel. Я прямо сказал, что ViewModels не должен его использовать, и предложил альтернативное решение, чтобы избежать его использования в ViewModel. - person Jackey; 27.08.2018

Попробуйте использовать SingleLiveEvent

Вот его код из репозитория образцов архитектуры Google (на случай, если он когда-либо будет удален из репо):

import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 * <p>
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 * <p>
 * Note that only one observer is going to be notified of changes.
 */
public class SingleLiveEvent<T> extends MutableLiveData<T> {

    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(LifecycleOwner owner, final Observer<T> observer) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }

        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    public void call() {
        setValue(null);
    }
}
person MidasLefko    schedule 27.08.2018

Вы даже можете передать экземпляр активности в модель или макет, но я не буду предпочитать это.

Предпочтительный способ - передать интерфейс макету строки.

объявить переменную в данных макета

<variable
    name="onClickListener"
    type="android.view.View.OnClickListener"/>

вызывать это при нажатии

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{onClickListener::onClick}"
    >

также установите этот слушатель из адаптера

 binding.viewModel = viewModel
 binding.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            context.startActivity(new Intent(context, MainActivity.class));
        }
    });
person Khemraj Sharma    schedule 18.09.2018