How using ViewBinding with an abstract base class

后端 未结 3 965
礼貌的吻别
礼貌的吻别 2020-12-10 20:31

I started using ViewBinding. After searching for some example or advice how to use ViewBinding with an abstract base class that should handle same logic on views expected to

3条回答
  •  佛祖请我去吃肉
    2020-12-10 21:28

    I found an applicable solution for my concrete scenario and I wan't to share it with you.

    Note that this is not an explaination on how ViewBinding works.

    I created some pseudo code below to share with you. (Migrated from my solution using DialogFragments that display an AlertDialog). I hope it's almost correct adapted to Fragments (onCreateView() vs. onCreateDialog()). I got it to work that way.

    Imagine we have an abstract BaseFragment and two extending classes FragmentA and FragmentB.

    First have a look at all of our layouts. Note that I moved out the reusable parts of the layout into a separate file that will be included later from the concrete fragment's layouts. Specific views stay in their fragment's layouts. Using a common used layout is important for this scenario.

    fragment_a.xml:

    
    
        
        
        
        
        
    
            
            
        
    
    

    fragment_b.xml:

    
    
        
        
        
        
        
    
            
            
        
    
    

    common_layout.xml

    
    
    
        

    Next the fragment classes. First our BaseFragment implementation.

    onCreateView() is the place where the bindings are inflated. We're able to bind the CommonLayoutBinding based on the fragment's bindings where the common_layout.xml is included. I defined an abstract method onCreateViewBinding() called on top of onCreateView() that returns the VewBinding from FragmentA and FragmentB. That way I ensure that the fragment's binding is present when I need to create the CommonLayoutBinding.

    Next I am able to create an instance of CommonLayoutBinding by calling commonBinding = CommonLayoutBinding.bind(binding.getRoot());. Notice that the root-view from the concrete fragment's binding is passed to bind().

    getCommonBinding() allows to provide access to the CommonLayoutBinding from the extending fragments. We could be more strict: the BaseFragment should provide concrete methods that access that binding instead of make it public to it's child-classes.

    private CommonLayoutBinding commonBinding; // common_layout.xml
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 
            @Nullable Bundle savedInstanceState) {
        // Make sure to create the concrete binding while it's required to 
        // create the commonBinding from it
        ViewBinding binding = onCreateViewBinding(inflater);
        // We're using the concrete layout of the child class to create our 
        // commonly used binding 
        commonBinding = CommonLayoutBinding.bind(binding.getRoot());
        // ...
        return binding.getRoot();
    }
    
    // Makes shure to create the concrete binding class from child-classes before 
    // the commonBinding can be bound
    @NonNull
    protected abstract ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater, 
            @Nullable ViewGroup container);
    
    // Allows child-classes to access the commonBinding to access common 
    // used views
    protected CommonLayoutBinding getCommonBinding() {
        return commonBinding;
    }
    

    Now have a look at one of the the child-classes, FragmentA. From onCreateViewBinding() we create our binding like we would do from onCreateView(). In principa it's still called from onCreateVIew(). This binding is used from the base class like described above. I am using getCommonBinding() to be able to access views from common_layout.xml. Every child class of BaseFragment is now able to access these views from the ViewBinding.

    That way I can move up all logic based on common views to the base class.

    private FragmentABinding binding; // fragment_a.xml
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 
            @Nullable Bundle savedInstanceState) {
        // Make sure commonBinding is present before calling super.onCreateView() 
        // (onCreateViewBinding() needs to deliver a result!)
        View view = super.onCreateView(inflater, container, savedInstanceState);
        binding.editName.setText("Test");
        // ...
        CommonLayoutBinding commonBinding = getCommonBinding();
        commonBinding.buttonUp.setOnClickListener(v -> {
            // Handle onClick-event...
        });
        // ...
        return view;
    }
    
    // This comes from the base class and makes sure we have the required 
    // binding-instance, see BaseFragment
    @Override
    protected ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater, 
            @Nullable ViewGroup container) {
        binding = FragmentABinding.inflate(inflater, container, false);
        return binding;
    }
    

    Pros:

    • Reduced duplicate code by moving it to the base class. Code in all fragments is now much more clear and reduced to their essentials
    • More clean layout by moving reusable views into a layout that's included via the

    Cons:

    • Possibly not exact applicable where views can't be moved into a commonly used layout file
      • Possibly views needs to be positioned different between frgments/layouts
      • Many layouts would result in many Binding classes, nothing to win then
    • Requires another binding instance (CommonLayoutBinding). There is not only one binding class for each child (FragmentA, FragmentB) that provides access to all views in the view hierarchy

    What if views can't be moved into a common layout?
    I am strongly interested in how to solve this as best practice! Lets think about it: introduce a wrapper class around the concrete ViewBinding. We could introduce an interface that provides access to commonly used views. From the Fragments we wrap our bindings in these wrapper-classes. This would result in many wrapper for each ViewBinding-type on the other hand. But we can provide these wrapper to the BaseFragment using an abstract method (an generics). BaseFragment is then able to access the views or work on them using the defined interface methods. What do you think?

    In conclusion:
    Maybe it's simply the actual limit of ViewBinding that one layout needs to have it's own Binding-class. If you found a good solution in cases the layout can't be shared and needs to be declared duplicated in each layout, let me know please.

    I don't know if this is best-practice or if there are better solutions. But until this is the only known solution for my use case it seems to be a good start!

提交回复
热议问题