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
Related
My app should be able to change color of one part of my screen in a Fragment (called Color Preview) from Color Picker that is DialogFragment and appears once a Button is clicked. I used the same ViewModel with MutableLiveData in it within Fragment and DialogFragment. However I am not able to get Data about color in Fragment when I pick some color in DialogFragment. Code below.
Dialog Fragment:
class ColorFragment : DialogFragment() {
private var _binding: FragmentColorBinding? = null
private val binding get() = _binding!!
private val viewModel: MainViewModel by lazy {
getViewModel {
MainViewModel()
}
}
#SuppressLint("DialogFragmentInsteadOfSimpleDialog")
override fun onStart() {
super.onStart()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
FragmentColorBinding.inflate(inflater, container, false).apply {
_binding = this
lifecycleOwner = this#ColorFragment
mainViewModel = getViewModel()
return root
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.color02.setOnClickListener {
viewModel.currentLightProfileColor.value = "#00ffff"
}
}
Main Fragment (where color should be shown) looks like this:
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
private var viewCreated = false
private val viewModel: MainViewModel by lazy {
getViewModel {
MainViewModel()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
FragmentMainBinding.inflate(inflater, container, false).apply {
_binding = this
lifecycleOwner = this#MainFragment
mainViewModel = getViewModel()
executePendingBindings()
viewCreated = true
return root
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.colorPreview.holder?.addCallback(this)
viewModel.currentLightProfileColor.observe(viewLifecycleOwner) { color ->
Color.parseColor(color.toString()).let { color ->
Timber.d("color LiveDataGet: ${color}")
binding.colorPreview.setBackgroundColor(color)
}
}
binding.color.setOnClickListener {
findNavController().navigate(R.id.ColorFragment)
}
}
In my ViewModel there is only MutableLiveData for color:
class MainViewModel : ViewModel() {
val currentLightProfileColor = MutableLiveData<String>()}
I dont get any errors but once I click on color2 I should get from Timber in Log: "color LiveDataGet: #00ffff" but I dont and color preview does not change color.
Did I miss something?
I would appreciate if someone could quickly take a look at my code. Thanks!
I found a solution. In Kotlin, data is not shared if viewModels() is instantiated without activityViewModels().
So I changed my code, instead of:
private val viewModel: MainViewModel by lazy {
getViewModel {
LightmvpViewModel()
}
}
I wrote:
private val viewModel: MainViewModel by activityViewModels()
With that simple change, everything should work.
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
}
LiveData is not observing on child viewpager fragments where the child fragments have one shared viewmodel to access data.
Here FragmentA and FragmentB are part of a viewpager and both of them are sharing one viemodel SharedViewModel.
public class SharedViewModel extends AndroidViewModel { //in Java
private final MutableLiveData<Data> mLiveData = new MutableLiveData<>();
public LiveData<Data> getLiveData() {
return mLiveData;
}
//for updating data through LiveData, using post as and when I get the response from DataSource as shown below.
mLiveData.postValue(response); //getting the response on debugging
}
class FragmentA : Fragment() { //in Kotlin
override fun onCreate(#Nullable savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
....
mViewModel.liveData.observe(viewLifecycleOwner, {
//no call coming in this block so unable to update view
})
}
}
class FragmentB : Fragment() { //in Kotlin
override fun onCreate(#Nullable savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
}
}
Need some help as to why the live data is not able to observe the changes.
Thanks in advance.
Your ViewModel is not shared. you have named it as sharedViewmodel but the way you are getting an instance of it by passing a Unique Owner it will also be a unique instance.
the correct way of sharing view model is
class SharedViewModel : ViewModel() {
private val myLiveData = MutableLiveData<Data>()
fun getMyLiveDta():LiveData {
return myLiveData
}
}
Now in the first fragment
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Get instance of viewmodel in fragment like this
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
And in the second fragment like this
class DetailFragment : Fragment() {
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
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) }
I'm trying to send data from DialogFragment to Fragment using ViewModel but it seems both fragment and Dialog fragment are referencing different instances of ViewModel. so I can't access data . Is there any way I can fix this issue? thanks
Here is my Fragment
#AndroidEntryPoint
class FragmentToReceiveData:BaseFragment(R.layout.fragment_1){
private val viewModel: AddScheduleViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d(TAG, "onViewCreated: $viewModel") // will print ...MyViewModel#62274cc
viewModel.dataFromDialog.observe(viewLifecycleOwner){
//nothing happens
}
}
.
.
.
private fun openDialog(){
val action=FragmentToReceiveDataDirections.actionFragmentToReceiveDataToExampleDialog()
findNavController().navigate(action)
//exampleDialog.show(requireActivity().supportFragmentManager, "alarmDialog") //same issue
}
}
Here is ViewModel:
class MyViewModel #ViewModelInject constructor(){
var dataFromDialog=MutableLiveData<SomeClass>()
fun saveDataFromDialog(data:SomeClass){
dataFromDialog.value=data
}
}
Here is my DialogFragment
#AndroidEntryPoint
class ExampleDialog:DialogFragment() {
val viewModel:MyViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog: $viewModel") // will print ...MyViewModel#125436
.
.
.
viewMode.saveDataFromDialog(data)
}
}
P.S: I'm using single activity architecture, So I'm not sure if activityViewModels() is a good idea
in order to share ViewModel between fragments, you can use activityViewModels(). for instance,
class SharedViewModel : ViewModel() {
...
}
class MasterFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
}
}
please read more in the android documentation here: https://developer.android.com/topic/libraries/architecture/viewmodel#sharing