Как использовать привязку данных с фрагментом

182

Я пытаюсь следовать примеру привязки данных из официального документа Google https://developer.android.com/tools/data-binding/guide.html

за исключением того, что я пытаюсь применить привязку данных к фрагменту, а не к деятельности.

ошибка, которую я сейчас получаю при компиляции

Error:(37, 27) No resource type specified (at 'text' with value '@{marsdata.martianSols}.

onCreate для фрагмента выглядит так:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MartianDataBinding binding = MartianDataBinding.inflate(getActivity().getLayoutInflater());
    binding.setMarsdata(this);
}

onCreateView для фрагмента выглядит так:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.martian_data, container, false);
}

и части моего файла макета для фрагмента выглядят так:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="marsdata"
            type="uk.co.darkruby.app.myapp.MarsDataProvider" />
    </data>
...

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="@{marsdata.martianSols}"
        />

    </RelativeLayout>
</layout>

Я подозреваю, что MartianDataBindingне знаю, с каким файлом макета он должен быть связан - отсюда и ошибка. Какие-либо предложения?

dark_ruby
источник

Ответы:

354

Реализация привязки данных должна быть в onCreateViewметоде фрагмента, удалить любые привязки данных, которые существуют в вашем OnCreateметоде, вы onCreateViewдолжны выглядеть так:

public View onCreateView(LayoutInflater inflater, 
                         @Nullable ViewGroup container, 
                         @Nullable Bundle savedInstanceState) {
    MartianDataBinding binding = DataBindingUtil.inflate(
            inflater, R.layout.martian_data, container, false);
    View view = binding.getRoot();
    //here data must be an instance of the class MarsDataProvider
    binding.setMarsdata(data);
    return view;
}
Hdioui Abdeljalil
источник
Мне пришлось добавить вызов super, чтобы сгенерировать мой класс Binding.
joey_g216
3
Я борюсь с этой проблемой часами. Проблема была в том, что я вернул неправильное мнение. +1
TharakaNirmana
1
View view = binding.getRoot(); Я застрял на этом так долго, что на законных основаниях расстроился, что не смог найти никакой документации по этому поводу на developer.android.com ... Решил проблему. Спасибо!
Виктор Удэ,
1
Если вы используете LiveData и ViewModel, обязательно прочитайте этот ответ .
Big McLargeHuge
1
что такое setMarsdata ()? я думаю, что здесь мы используем setViewModel () ??
внедорожник
59

Вам действительно рекомендуется использовать inflateметод сгенерированного Binding, а не DataBindingUtil:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    MainFragmentBinding binding = MainFragmentBinding.inflate(inflater, container, false);
    //set variables in Binding
    return binding.getRoot();
}

Документы для DataBindingUtil.inflate () :

Используйте эту версию, только если layoutId заранее неизвестен. В противном случае используйте сгенерированный метод инфляции Binding, чтобы обеспечить безопасную инфляцию.

Пока
источник
К сожалению, это убивает меня из-за cannot be resolved to a typeошибки при сборке. Это не надежно на мой взгляд. Если я сначала выберу, DataBindingUtil.inflate(inflater, R.layout.fragment_camera, container, false);а затем изменил его на FragmentCameraBinding.inflate(inflater, container, false);, он работает, но после перестроения снова выдает ошибку.
Алекс Бурдусел
Прекрасно работает. На самом деле нет необходимости указывать идентификатор размещения макета (что мне было интересно раньше), так как он автоматически выбирает из сгенерированного файла привязки.
eC Droid
2
где вы устанавливаете идентификатор макета фрагмента (например, R.layout.fragment_) в этом примере?
Ленин Радж Раджасекаран
это должен быть принятый ответ. рекомендуется использовать макет сгенерированного связывания вместоDataBindingUtil.inflate
mochadwi
@LeninRajRajasekaran Идентификатор макета подразумевается через использование MainFragmentBindingкласса. Этот класс создается из файла макета, поэтому автоматически применяется нужный макет.
Эмиль С.
19

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

Используйте Binding class's inflateв соответствии с рекомендациями в документации Android .

Один из вариантов - надуть, DataBindingUtil но когда только вы не знаете, сгенерирован класс привязки .

- Вы создали автоматически binding class, используйте этот класс вместо использования DataBindingUtil.

На яве

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    HomeFragmentBinding binding = HomeFragmentBinding.inflate(inflater, container, false);
    //set binding variables here
    return binding.getRoot();
}

В котлине

lateinit var binding: HomeFragmentBinding 
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    binding = HomeFragmentBinding.inflate(inflater, container, false)
    return binding.root
}

В документации класса DataBindingUtil вы можете увидеть.

надуйте

T inflate (LayoutInflater inflater, 
                int layoutId, 
                ViewGroup parent, 
                boolean attachToParent)

Используйте эту версию, только если layoutId заранее неизвестен. В противном случае используйте сгенерированный метод инфляции Binding, чтобы обеспечить безопасную инфляцию.

Если ваш класс связывания макета не сгенерирован @ См. Этот ответ .

Khemraj
источник
почему бы не использовать inflateметод, который принимает в LayoutInflaterкачестве единственного аргумента?
Флориан Вальтер
@FlorianWalther это работает без ViewGroup container?
Хемрай
Ну, я не знал, когда я написал этот комментарий. Но я получил хороший ответ здесь: stackoverflow.com/questions/61571381/…
Флориан Вальтер
1
@FlorianWalther хорошо, я прочитал ответ, который containerнужен, когда attachToRootесть true.
Хемрай
16

Если вы используете ViewModel и LiveData Это достаточный синтаксис

Синтаксис Котлина:

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return MartianDataBinding.inflate(
        inflater,
        container,
        false
    ).apply {
        lifecycleOwner = viewLifecycleOwner
        vm = viewModel    // Attach your view model here
    }.root
}
Саман Саттари
источник
10

Попробуйте это в Android DataBinding

FragmentMainBinding binding;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
        View rootView = binding.getRoot();
        initInstances(savedInstanceState);
        return rootView;
}
Jirawat Harnsiriwatanakit
источник
7

Можно просто получить вид объекта, как указано ниже

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View view = DataBindingUtil.inflate(inflater, R.layout.layout_file, container, false).getRoot();

return view;

}
Имран Соланки - GSLab
источник
7

Синтаксис Котлина:

lateinit var binding: MartianDataBinding
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    binding = DataBindingUtil.inflate(inflater, R.layout.martian_data, container, false)
    return binding.root
}
muneikh
источник
7

Как и многие другие, но не забудьте установить LifeCycleOwner
Sample в Java, т.е.

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    BindingClass binding = DataBindingUtil.inflate(inflater, R.layout.fragment_layout, container, false);
    ModelClass model = ViewModelProviders.of(getActivity()).get(ViewModelClass.class);
    binding.setLifecycleOwner(getActivity());
    binding.setViewmodelclass(model);

    //Your codes here

    return binding.getRoot();
}
Левша
источник
5

работает в моем коде.

private FragmentSampleBinding dataBiding;
private SampleListAdapter mAdapter;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    dataBiding = DataBindingUtil.inflate(inflater, R.layout.fragment_sample, null, false);
    return mView = dataBiding.getRoot();
}
UJWAL GHONGADE
источник
5

Полный пример фрагментов привязки данных

FragmentMyProgramsBinding - это класс привязки, созданный для res / layout /gment_my_programs

public class MyPrograms extends Fragment {
    FragmentMyProgramsBinding fragmentMyProgramsBinding;

    public MyPrograms() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
    FragmentMyProgramsBinding    fragmentMyProgramsBinding = DataBindingUtil.inflate(inflater, R
                .layout.fragment_my_programs, container, false);
        return fragmentMyProgramsBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

    }
}
Вивек Ядав
источник
2

Очень полезный блог о привязке данных: https://link.medium.com/HQY2VizKO1

class FragmentBinding<out T : ViewDataBinding>(
    @LayoutRes private val resId: Int
) : ReadOnlyProperty<Fragment, T> {

    private var binding: T? = null

    override operator fun getValue(
        thisRef: Fragment,
        property: KProperty<*>
    ): T = binding ?: createBinding(thisRef).also { binding = it }

    private fun createBinding(
        activity: Fragment
    ): T = DataBindingUtil.inflate(LayoutInflater.from(activity.context),resId,null,true)
}

Объявите привязку val следующим образом во Fragment:

private val binding by FragmentBinding<FragmentLoginBinding>(R.layout.fragment_login)

Не забудьте написать это во фрагменте

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return binding.root
}
Дев Сони
источник
1

Еще один пример в Котлине:

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val binding = DataBindingUtil
            .inflate< MartianDataBinding >(
                    inflater,
                    R.layout.bla,
                    container,
                    false
            )

    binding.modelName = // ..

    return binding.root
}

Обратите внимание, что имя «MartianDataBinding» зависит от имени файла макета. Если файл называется «martian_data», то правильное имя будет MartianDataBinding.

akohout
источник
0

Все говорят о inflate(), но что, если мы хотим использовать это в onViewCreated()?

Вы можете использовать bind(view)метод конкретного связующего класса, чтобы получить ViewDataBindingэкземпляр для view.


Обычно мы пишем BaseFragment примерно так (упрощенно):

// BaseFragment.kt
abstract fun layoutId(): Int

override fun onCreateView(inflater, container, savedInstanceState) = 
    inflater.inflate(layoutId(), container, false)

И использовать его в дочернем фрагменте.

// ConcreteFragment.kt
override fun layoutId() = R.layout.fragment_concrete

override fun onViewCreated(view, savedInstanceState) {
    val binding = FragmentConcreteBinding.bind(view)
    // or
    val binding = DataBindingUtil.bind<FragmentConcreteBinding>(view)
}


Если все фрагменты используют привязку данных, вы даже можете упростить их, используя параметр типа.

abstract class BaseFragment<B: ViewDataBinding> : Fragment() {
    abstract fun onViewCreated(binding: B, savedInstanceState: Bundle?)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        onViewCreated(DataBindingUtil.bind<B>(view)!!, savedInstanceState)
    }
}

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

Tura
источник