Cómo usar el enlace de datos con Fragment

182

Estoy tratando de seguir el ejemplo de enlace de datos del documento oficial de Google https://developer.android.com/tools/data-binding/guide.html

excepto que estoy tratando de aplicar la oferta de datos a un fragmento, no a una actividad.

el error que obtengo actualmente al compilar es

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

onCreate para el fragmento se ve así:

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

onCreateView para el fragmento se ve así:

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

y partes de mi archivo de diseño para fragmentos se ven así:

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

mi sospecha es que MartianDataBindingno sabe con qué archivo de diseño debe estar vinculado, de ahí el error. ¿Alguna sugerencia?

dark_ruby
fuente

Respuestas:

354

La implementación del enlace de datos debe estar en el onCreateViewmétodo del fragmento, elimine cualquier enlace de datos que exista en su OnCreatemétodo, onCreateViewdebería verse así:

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
fuente
Tuve que agregar la llamada a super para que se genere mi clase Binding.
joey_g216
3
Lucho con este problema durante horas. El problema fue que devolví la vista equivocada. +1
TharakaNirmana
1
View view = binding.getRoot(); He estado atrapado en esto durante tanto tiempo que estoy legítimamente bastante molesto porque no pude encontrar ninguna documentación al respecto en developer.android.com ... Resolvió el problema. ¡Gracias!
Victor Ude
1
Si está utilizando LiveData y ViewModel, asegúrese de leer esta respuesta .
Big McLargeHuge
1
¿Qué es setMarsdata ()? Creo que aquí usamos setViewModel () ??
suv
59

En realidad, se le recomienda utilizar el inflatemétodo de su enlace generado y no el 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();
}

Documentos para DataBindingUtil.inflate () :

Use esta versión solo si layoutId es desconocido de antemano. De lo contrario, utilice el método de inflado de Binding generado para garantizar una inflación segura.

Hasta que
fuente
Desafortunadamente esto me está matando con el cannot be resolved to a typeerror en la compilación. No es confiable en mi opinión. Si voy primero con DataBindingUtil.inflate(inflater, R.layout.fragment_camera, container, false);y luego lo cambio a FragmentCameraBinding.inflate(inflater, container, false);, funciona, pero después de reconstruir da el error nuevamente.
Alex Burdusel
Funciona genial. En realidad, no es necesario especificar la identificación del diseño (que me preguntaba antes), ya que se selecciona automáticamente del archivo de enlace generado.
eC Droid
2
¿Dónde establece la identificación del diseño del fragmento (por ejemplo, R.layout.fragment_) en este ejemplo?
Lenin Raj Rajasekaran
Esta debería ser la respuesta aceptada. se recomienda utilizar el enlace generado por el diseño, en lugar deDataBindingUtil.inflate
mochadwi
@LeninRajRajasekaran La identificación del diseño está implícita a través del uso de la MainFragmentBindingclase. Esa clase se genera a partir del archivo de diseño, por lo que el diseño deseado se aplica automáticamente.
Emil S.
19

Incluso las otras respuestas pueden funcionar bien, pero quiero decir el mejor enfoque.

Use Binding class's inflatecomo se recomienda en la documentación de Android .

Una opción es inflar por DataBindingUtil pero cuando solo usted no sabe haber generado una clase de enlace .

--Has generado automáticamente binding class, usa esa clase en lugar de usar DataBindingUtil.

En java

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

En kotlin

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

En la documentación de la clase DataBindingUtil puede ver.

inflar

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

Use esta versión solo si layoutId es desconocido de antemano. De lo contrario, utilice el método de inflado de Binding generado para garantizar una inflación segura.

Si su clase de agrupación de diseño no se genera @Vea esta respuesta .

Khemraj
fuente
¿Por qué no utilizar el inflatemétodo que toma el LayoutInflatercomo su único argumento?
Florian Walther
@FlorianWalther ¿funciona sin eso ViewGroup container?
Khemraj
Bueno, no sabía cuándo escribí este comentario. Pero obtuve una buena respuesta aquí: stackoverflow.com/questions/61571381/…
Florian Walther
1
@FlorianWalther está bien, he leído la respuesta, eso containeres necesario cuando attachToRootestá true.
Khemraj
16

Si está utilizando ViewModel y LiveData Esta es la sintaxis suficiente

Sintaxis de Kotlin:

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
}
Saman Sattari
fuente
10

Prueba esto en 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
fuente
7

Uno simplemente puede recuperar el objeto de vista como se menciona a continuación

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;

}
Imran Solanki - GSLab
fuente
7

Sintaxis de Kotlin:

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

Como la mayoría ha dicho, pero no se olvide de configurar LifeCycleOwner
Sample en Java, es decir

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();
}
Zurdo
fuente
5

trabajando en mi código

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
fuente
5

Un ejemplo completo en fragmentos de enlace de datos

FragmentMyProgramsBinding es una clase de enlace generada para res / layout / fragment_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);

    }
}
vivek yadav
fuente
2

Blog muy útil sobre enlace de datos: 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)
}

Declara un enlace val como este en Fragment:

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

No olvides escribir esto en fragmentos

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return binding.root
}
Dev Soni
fuente
1

Otro ejemplo en Kotlin:

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
}

Tenga en cuenta que el nombre "MartianDataBinding" depende del nombre del archivo de diseño. Si el archivo se llama "martian_data", el nombre correcto sería MartianDataBinding.

akohout
fuente
0

Todos dicen acerca de inflate(), pero ¿qué pasa si queremos usarlo onViewCreated()?

Puede usar el bind(view)método de la clase de enlace concreto para obtener una ViewDataBindinginstancia para view.


Por lo general, escribimos BaseFragment de esta manera (simplificado):

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

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

Y úsalo en un fragmento infantil.

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


Si todos los Fragmentos usan enlace de datos, incluso puede simplificarlo usando el parámetro tipo.

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

No sé si está bien afirmar que no es nulo allí, pero ... entiendes la idea. Si quieres que sea anulable, puedes hacerlo.

Tura
fuente