I'm trying to use Dagger hilt in my project. I have an Activity that uses Databinding:
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), SetGreeting {
private lateinit var binding: ActivityMainBinding
#Inject
lateinit var fragmentFactory: FragmentsFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
supportFragmentManager.fragmentFactory = fragmentFactory
...
}
override fun greeting(msg: String) {
binding.greeting.text = msg
}
}
this is how I use greeting interface:
interface SetGreeting {
fun greeting(msg: String)
}
#Module
#InstallIn(ActivityComponent::class)
object SetGreetingModule {
#Provides
fun provideGreeting(): SetGreeting {
return MainActivity()
}
}
which would be used inside of a fragment just like this:
#AndroidEntryPoint
class MainFragment : Fragment() {
private val viewModel: MainViewModel by viewModels()
private lateinit var binding: FragmentMainBinding
#Inject
lateinit var greetings: SetGreeting
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.greeting.observe(viewLifecycleOwner, {
it?.let { msg ->
greetings.greeting(msg)
}
})
}
}
The problem is that when I added Dagger to the project, data binding won't work, and it returns null. So when the override function greeting would be called, I get a null pointer exception.
I think dagger does not call onCreate so
binding = DataBindingUtil.setContentView(this, R.layout.activity_main) is not called then binding will be null.
try to initialize the binding object in init block
init{binding = DataBindingUtil.setContentView(this, R.layout.activity_main) }
Related
I used before view Binding to bind layout with activity or fragment, I saw that after Fragment 1.1.0 we can bind layout in constructor, which is performance. What android says.
Activity
With binding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
With constructor
class MainActivity : AppCompatActivity(R.layout.activity_main)
Fragment
With binding
class MyFragment: Fragment() {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
}
With constructor
class MyFragment: Fragment(R.layout.my_fragment) {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = MyFragmentBinding.bind(view)
}
}
I have my MainActivity where there are the movie with their poster. If I click on a poster (in the layout there are 4 ImageView) I can read the plot (a TextView) that is in the Fragment. If I open my app with the emulator MainActivity and Fragment are overlapping so I need to set the ViewModel. How to set it?
//MainActivity
class JacksonActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.jackson_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.jackson_film, JacksonFragment.newInstance())
.commitNow()
}
}
//Fragment
class JacksonFragment : Fragment() {
companion object {
fun newInstance() = JacksonFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return inflater.inflate(R.layout.jackson_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
// TODO: Use the ViewModel
} }
ViewModelProvider is deprecated. more info: ViewModelProvider
To implement viewModel simply use val viewModel: MainViewModel by viewmodels()
if you use the same viewmodel in more than one place(acitity/fragment) in fragment use val viewModel: MainViewModel by activityViewModels()
Note: do not forget to update the dependencies. More info: ViewModel / SharedViewModel
So, I am trying to migrate from kotlin synthetic to Jetpack view binding.
Here is the kotlin synthetic code (works fine) that simply set visibility to invisible of TextView in the parent activity from fragment.
import kotlinx.android.synthetic.main.activity_main.*
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_first, container, false)
requireActivity().textView.visibility = View.INVISIBLE
return view
}
}
And here is what I'm doing to migrate:
import com.mypc.myapp.databinding.FragmentFirstBinding
import com.mypc.myapp.databinding.ActivityMainBinding
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textView.visibility = View.INVISIBLE
binding.textview.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.goto_secondfragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I'm getting error as 'Unsolved reference' at 'textview':
binding.textView.visibility = View.INVISIBLE
And at:
binding.textview.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.goto_secondfragment)
}
Obviously the compiler is not able to find TextView that is in Activity
I've added this line:
import com.mypc.myapp.databinding.ActivityMainBinding
Since your binding is private to MainActivity you can refer to your textView from the MainActivity only. To show/hide this view from FirstFragment you can create a public function in MainActivity and call it from your FirstFragment.
class MainActivity: AppCompatActivity {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
fun showHideTextView(visible: Boolean) {
binding.textView.isVisible = visible
}
}
And in your fragment, you can call:
(requireActivity() as MainActivity).showHideTextView(false) // This will hide the textView
First of all, you should define instance of activity view binding in baseActivity which is a parent class of your MainActivity, and then define method to change your text view like 'showTextView' , after that in the base fragment class initalize base activity instance with casting context object in onAttach method.
I provide you some code:
abstract class BaseRegisterActivity : BaseActivity() {
//---
protected lateinit var binding: ActivityRegisterBinding
private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as
NavHostFragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_register)
//---
}
fun showTextView() {
binding.textView.visibility = View.VISIBLE
binding.textView.setOnClickListener {
val fragment =
navHostFragment.childFragmentManager.fragments[0]
if (fragment is FirstFragment) {
//todo
}
}
}
}
abstract class BaseFragment : Fragment(), Injectable {
//--
lateinit var baseActivity: BaseRegisterActivity
override fun onAttach(context: Context) {
super.onAttach(context)
baseActivity = context as BaseRegisterActivity
}
//--
}
class ShopFragment : Fragment() {
var binding:FragmentShopBinding ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentShopBinding.inflate(inflater,container,false)
return binding?.root
}
I have a parent fragment which fetches a list from API using ViewModel and Retrofit, the ViewModel is injected with Hilt.
After the list gets fetched the parent fragment will pass to its child fragment that is inside of parent fragment.
but the problem is that ViewModel is instantiated one more time in the child fragment.
Parent Fragment
#AndroidEntryPoint
class ParentFragment : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
mViewModel.liveData.observe(this, { data ->
{
childFragmentManager.beginTransaction().apply {
replace(
mViewBinding.fragmentContainer.id,
ChildFragment(data)
)
}
commit()
} })
mViewModel.getURL("TEST", "2021-06-18", "2021-07-18", 1 , 0 , -1, false)
return mViewBinding.root
}
}
ChildFragment
#AndroidEntryPoint
class ChildFragment(val data: List<Item>) : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
// mViewModel is instantiated again and some all strings properties of it is null.
return mViewBinding.root
}
}
URLViewModel
#HiltViewModel
class URLViewModel #Inject constructor(private val urlApi: URLApi): ViewModel() {
private val _urlLiveData = MutableLiveData<State<Any?>>()
val urlLiveData: LiveData<State<Any?>> = _urlLiveData
var urlName: String? = null
var beginDate: String? = null
var endDate: String? = null
var adultCount = 0
var childrenCount = 0
var airportId = 0
var isRoundTrip = false
init {
Log.e("URLViewModel", "iniialed again" )
}
#ExperimentalStdlibApi
fun getUrl(urlName: String, beginDate: String, endDate: String, adultCount: Int, childCount: Int, airportId: Int, isRoundTrip: Boolean){
Log.e("XXXXXX", "getUrl: called with url of " + urlName )
this.urlName = urlName
this.beginDate = beginDate
this.endDate = endDate
this.adultCount = adultCount
this.childrenCount = childCount
this.airportId = airportId
this.isRoundTrip = isRoundTrip
val mutableLiveData = MutableLiveData<State<Any?>>()
mutableLiveData.value = State.loading()
viewModelScope.launch {
val res = urlApi.getURL(urlName,beginDate,endDate,adultCount,childCount,airportId,isRoundTrip)
Log.e("URLVIewModel", "getUrl: response received" )
_urlLiveData.value = res
}
}
}
when I wanna access some properties like beginDate, they are null, because the ViewModel is instantiated again,
viewModels() delegation create view model against the same instance i.e Fragment's instance in your case. What you need to do is to create a shared View model .
There is helper delegate available for it with ktx libraries.
add the ktx dependency which you already have i guess from here.
implementation "androidx.fragment:fragment-ktx:1.3.4"
And create view model with
private val viewModel by activityViewModels<UrlViewModel>()
You do not have to use activity shared view model. Simply request view model from parent fragment in ChildFragment.
private val viewModel by viewModels<UrlViewModel>(ownerProducer = { requireParentFragment() })
You are trying to use share viewmodel. Try to following code for reference.
#AndroidEntryPoint
class ParentFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
#AndroidEntryPoint
class ChildFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
I'm dev-ing activity using dagger. In my fragment, I can use this code as below. but when I use this code in activity, I cannot use this code.
private val viewModel by viewModels<NoticeViewModel> { viewModelFactory }
As result I can't initialize viewmodel. how can I initialize activity using dagger?
fragment
class NoticeFragment : DaggerFragment() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel by viewModels<NoticeViewModel> { viewModelFactory }
private lateinit var viewDataBinding: FragmentNoticeBinding
private var notice = ""
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_notice, container, false)
viewDataBinding = FragmentNoticeBinding.inflate(inflater, container, false).apply {
viewmodel = viewModel
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
init()
viewModel.getNotice()
}
private fun init(){
viewModel.notice.observe(this, Observer{
noticeMain.text = it
})
}
}
activity
class ScheduleDialog : DaggerActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel by viewModels<ScheduleDialogViewModel> { viewModelFactory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_schedule_dialog)
//viewDataBinding = DataBindingUtil.setContentView( this,R.layout.activity_schedule_dialog)
viewModel.getScheduleById(5)
}
}
MyComponent component = DaggerMyComponent.builder().build();
component.inject(this);
For dependency injection you should have the following code, also in the component code should be
void inject(YourActivity activity);