How to delegate scope in Kotlin - kotlin

I have a method onBind() that I want to use with a scope of binding, is there a way to achieve this?
Currently, I have a variable (“binding: T”) and an abstract function onBind() in BaseBindableFragment, and in my implementation I use with(binding){} to scope onBind(). I want to avoid using with(binding) and make it so that in implementation I get onBind() { this: T -> }
My abstract class
abstract class BaseBindableFragment<T : ViewDataBinding> : Fragment() {
protected lateinit var binding: T
private set
protected abstract val layout: Int
#LayoutRes get
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, layout, container, false)
// Wrap with(binding){onBind()} in a way that `onBind` executes with `binding` as `this`
onBind()
return binding.root
}
protected abstract fun onBind()
}
And then in my implementation
override fun onBind() = with(binding) { // <- I want to avoid this "= with(binding)" line
lifecycleOwner = viewLifecycleOwner
viewModel = myViewModel
}

protected abstract fun T.onBind()
and
binding = DataBindingUtil.inflate(inflater, layout, container, false)
binding.onBind()
return binding.root
The implementation becomes
override protected fun T.onBind() {
lifecycleOwner = viewLifecycleOwner
viewModel = myViewModel
}
(or e.g. MyBinding.onBind() if your class extends BaseBindableFragment<MyBinding>)

Related

Recycler View and DialogFragment struggling (Kotlin)

Hello i am using a recyclerView to swipe and call for a Dialog fragment
The the idea is that when i dismiss the dialog Fragment the raw come back to the original place
but the problem is that it return, just after the dialog is opened.
So i do not how to call notifyItemChanged inside the dialogFragment, or implement the dialogFragment.setOnDismissListener because when i override i dont know how to pass to the function the adapter to call notifyItemChanged i
This is my code
class Fragmento : Fragment(), RecyclerAdapter.ClickListener {
// TODO: Rename and change types of parameters
private lateinit var adapter :RecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view =inflater.inflate(R.layout.fragment_fragmento, container,false)
initRecyclerView(view)
return view
}
private fun initRecyclerView(view: View) {
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager=LinearLayoutManager(activity)
adapter = RecyclerAdapter()
recyclerView.adapter=adapter
val itemSwipe=object:ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment()
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
}
val swap =ItemTouchHelper(itemSwipe)
swap.attachToRecyclerView(recyclerView)
}
private fun showDialog(viewHolder: RecyclerAdapter.ViewHolder){
val builder= AlertDialog.Builder(activity)
builder.setTitle("DeleteItem")
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance() =
Fragmento().apply {
arguments = Bundle().apply {
}
}
}
}
and for the DialogFragment
class DialogFragment: DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
}
}
If I have understand the problem here is that you want to call notifyItemChanged() when your dialog is dismiss without setOnDismissListener.
If you want to do that you can simply add a callback on your DialogFragment and use it in your recyclerView.
class DialogFragment(dismiss: () -> Unit): DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
dismiss()
}
}
When you are creating the DialogFragment you can use the callback :
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment() {
//here you are notified when the dialog is dismiss
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}

Share ViewModel used by calling Fragment to DialogFragment using by viewModels

I have a SearchFragment with the following code.
#AndroidEntryPoint
class SearchFragment :
Fragment(),
View.OnClickListener {
...
private var _binding: FragSearchBinding? = null
private val binding get() = _binding as FragSearchBinding
private val viewmodel by viewModels<SearchViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
)
: View {
_binding = FragSearchBinding.inflate(inflater, container, false)
binding.fragSearchSearchResultFilter.setOnClickListener(this)
return binding.root
}
...
private fun showFilterDialog() {
val dialog = FilterBottomSheetDialogFragment.newInstance()
dialog.show(parentFragmentManager, "filter_bsd_tag")
}
...
}
I am showing a FilterBottomSheetDialogFragment using that SearchFragment. I want to pass the ViewModel of SearchFragment to the DialogFragment. I have this code for my FilterBottomSheetDialogFragment.
#AndroidEntryPoint
class FilterBottomSheetDialogFragment :
BottomSheetDialogFragment(),
View.OnClickListener {
companion object {
fun newInstance() = FilterBottomSheetDialogFragment()
private const val TAG_SELECTION_DIALOG = "tag_selection_dialog"
}
private var _binding: BsdFilterBinding? = null
private val binding get() = _binding as BsdFilterBinding
private val viewmodel: SearchViewModel = ???
}
I have tried
private val viewmodel by viewModels<SearchViewModel>(ownerProducer = { this.requireParentFragment() })
The above doesn't work as it just creates a new instance of ViewModel.
I also tried
private val viewmodel: SearchViewModel by lazy {
ViewModelProvider(requireParentFragment()).get(SearchViewModel::class.java)
}
The above doesn't work with the error saying that the SearchViewModel instance cannot be created. My SearchViewModel has this constructor.
#HiltViewModel
class SearchViewModel #Inject constructor(
private val courseRepository: CourseRepository
) : ViewModel()
How can I pass the SearchViewModel to the DialogFragment without using the constructor parameter?
You're mistake here is actually the fragment manager you're using when showing your dialog. Currently you're using the parent fragment manager, whereas your dialog should exist as a child fragment of the fragment it is being shown from.
So you should be using:
dialog.show(childFragmentManager, "filter_bsd_tag")
which will then ensure that
viewModels<SearchViewModel>(ownerProducer = { requireParentFragment() })
refers to the SearchFragment.

Layout view is not initialized with Butterknife

In the following code:
class LobbyFragment : Fragment() {
#Inject
lateinit var lobbyFragmentHelloService: LobbyFragmentHelloService
#BindView(R.id.lobby_fragment_hello)
lateinit var lobbyFragmentHelloTextView: TextView
lateinit var unbinder: Unbinder
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.lobby_fragment, container, false)
unbinder = ButterKnife.bind(this, view)
return view
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sayFragmentHello()
}
override fun onAttach(context: Context?) {
AndroidInjection.inject(this)
super.onAttach(context)
}
override fun onDestroyView() {
super.onDestroyView()
unbinder.unbind()
}
private fun sayFragmentHello() {
lobbyFragmentHelloTextView.text = lobbyFragmentHelloService.sayHello()
}
}
lobbyFragmentHelloTextView is never initialized. Butterknife is used to initialize this variable. Why is not initialized by the time sayFramentHello is called?
I'm not really sure what went wrong but to fix the issue, you can consider using kotlin built in synthetic binding and just get rid of butterknife. It's more efficient.
explained here

fragment cannot be provided without an #Provides-annotated method. Kotlin

My Application class
class MyApp : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
/*#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>*/
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder().application(this).build().inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> =
activityInjector
//override fun supportFragmentInjector(): AndroidSupportInjection<Fragment>
= fragmentInjector
}
App Component
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class,
AppModule::class, BuilderModule::class))
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApp)
}
App Module
#Module
class AppModule {
#Provides
#Singleton
fun provideUtil(application: Application): Utils = Utils(application)
}
Builder Module
#Module
abstract class BuilderModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
#ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
}
Main Activity
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fragmentInjector
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this); // Call before super!
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(HomeFragment())
}
fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frameContainer, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
Home Fragment
class HomeFragment : Fragment() {
#Inject
lateinit var utils: Utils
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this) // Providing the dependency
super.onAttach(context)
}
override fun onCreateView(inflater: LayoutInflater, container:
ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
utils.toaster("Injected")
}
I am getting this error
D:\Workspace\DaggerKotlin\app\build\tmp\kapt3\stubs\debug\com\cvsingh\daggerkotlin\di\AppComponent.java:8: error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent {
^
java.util.Map>> is injected at
dagger.android.DispatchingAndroidInjector.(…, injectorFactoriesWithStringKeys)
dagger.android.DispatchingAndroidInjector is injected at
com.cvsingh.daggerkotlin.ui.MainActivity.fragmentInjector
com.cvsingh.daggerkotlin.ui.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.cvsingh.daggerkotlin.di.AppComponent ? com.cvsingh.daggerkotlin.di.BuilderModule_ContributeMainActivity.MainActivitySubcomponent
}
You're doing it wrong with your MyApp class, you should use DaggerApplication to simplify you dagger integration :
class MyApp : DaggerApplication() {
private val applicationInjector = DaggerApplicationComponent.builder()
.application(this)
.build()
override fun applicationInjector() = applicationInjector
}
And for your information you can simplify the same way your activites and fragments using DaggerAppCompatActivity and DaggerFragment
for example your activity could be :
class MainActivity : DaggerAppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(HomeFragment())
}
fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frameContainer, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
If you don't want to use DaggerApplication, DaggerAppCompatActivity, etc.. (sometimes you need to extends from another class) then simply copy paste code from DaggerApplication, DaggerAppCompatActivity, etc in your class

Field overriding or constructor param which is better

Which base fragment prefer to use and why?
In this implementation layoutRes is abstract field.
abstract class BaseFragment1 : Fragment() {
abstract val layoutRes: Int
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(layoutRes, container, false)
}
}
And in this implementation layoutRes is passing through constructor
abstract class BaseFragment2(#LayoutRes private val layoutRes: Int) : Fragment() {
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(layoutRes, container, false)
}
}
I want to know which implementation is better to use? If you have another solution you can share it.
Example of implementations:
class FramgnetA : BaseFragment1() {
override val layuotRes = R.layout.layout
}
class FragmentB : BaseFragment2(R.layout.layout)
Like in Best practice for instantiating a new Android Fragment - for parameters that can be supplied from the outside you can use a Bundle and Fragment#setArguments(Bundle)
E.g.
class DynamicContentFragment : Fragment() {
companion object {
private const val KEY_LAYOUT_ID = "layoutId"
fun instance(#LayoutRes layoutRes: Int) =
DynamicContentFragment().apply {
arguments = Bundle().apply { putInt(KEY_LAYOUT_ID, layoutRes) }
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val layout = arguments!!.getInt(KEY_LAYOUT_ID)!!
return inflater.inflate(layout, container, false)
}
}
class UseCase {
fun test(fm: FragmentManager) {
fm.beginTransaction()
.replace(R.id.container, DynamicContentFragment.instance(R.layout.main))
}
}
Otherwise your solutions are fine but it require a new class per parameter. Classes are cheap to write in kotlin so it's preference I guess.
Recently google added this overload of the fragment constructors. I think now, everything very obvious.