How do you initiate Realm in Fragment? - android

I needed to initialize Realm from fragment but I get the following warning:
Required: Context
Found: Context
Code:
class MyRealm: Fragment() {
private lateinit var realm : Realm
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_realm, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Realm.init(context)
val configuration = RealmConfiguration.Builder()
.name("MyRealm.db")
.deleteRealmIfMigrationNeeded()
.schemaVersion(0)
.build()
Realm.setDefaultConfiguration(configuration)
realm = Realm.getDefaultInstance()
}
}
What did I do wrong?

Opening and closing the realm within try/catch block is recommended
try {
Realm realm = Realm.getDefaultInstance();
//Use the realm instance
}catch(Exception e){
//handle exceptions
}finally {
realm.close();
}
Also check for minSdkVersion >= 19 and Java >= 7
You can go through the official documentation for more details https://realm.io/docs/java/latest/#closing-realms
Also use getActivity() in place of (context).
The activity is a context (since Activity extends Context).

Related

Setting setOnClickListener using bindings android kotlin

I have been doing a tutorial that is a bit out of date and uses synthetics rather than bindings. I am trying to use bindins. I am trying to set up a listener in a fragment (AddEditFragment.kt). It's using a callback to MainActivity.onSaveClicked.
In AddEditFragment I use an import for the binding
import com.funkytwig.tasktimer.databinding.FragmentAddEditBinding
I have a lateinit on the first line of the class defenition
class AddEditFragment : Fragment() {
private lateinit var binding: FragmentAddEditBinding
I am initializing the bunding in onActivityCreated and setting up the listner. I can use findViewById to get the ID
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val addEditSave = view?.findViewById(R.id.addEditSave) as Button
addEditSave.setOnClickListener { listener?.onSaveClicked() }
}
And this works fine but if I try to use the binding
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.addEditSave.setOnClickListener { listener?.onSaveClicked() }
}
The code does not show any errrors but it does not seem to create the listner. I have a Log.d in the onSaveClicked callback function and when I use the first (findViewById) version of the function it works (it calles onSaveClicked) but with the second version (using bindings) onSaveClicked does not get called when I click the Button.
I Cant figre out why the second version does not work, I thought the two versions of onActivityCreated should do the same thing.
The interface in AddEditFragment.kt is
interface OnSaveClicked {
fun onSaveClicked()
}
In fragment you should add your view in onCreateView or in OnViewCreated not in onActivityCreated
Please refer link for more details.
private var _binding: FragmentAddEditBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddEditBinding.inflate(inflater, container, false)
val view = binding.root
binding.addEditSave.setOnClickListener { listener?.onSaveClicked() }
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
OK, thanks for all the help. turned out I was doing the inflate wrong.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d(TAG, "onCreateView")
binding = FragmentAddEditBinding.inflate(layoutInflater, container, false)
return binding.root
}
I was doing
binding = FragmentAddEditBinding.inflate(layoutInflater)
I missed out on the last 2 args as I was taking the code from the inflate when I am in an Activity, not a Fragment. I think it is to do with the layout effecticly being in the parent.

viewBinding not making any changes inside fragment

My Goal
I am trying to access the widget that was created inside my fragment using viewBinding.
What I have done / Info about my app
The language I am using is kotlin.
I have already added the code below into gradle:
buildFeatures{
dataBinding = true
viewBinding = true
}
I have tested binding.aTextView.setText("Code working.") inside my main activity and it works.
What's the problem
I have tested the setText code inside activity and it works. The problem right now is the same code when I move into the fragment it wouldn't work. And I am sure that the code has been executed as I putted a toast above it and the toast executed successfully which mean it should have at least reached that point before but not sure due to what reason there wasn't any changes.
My mainActivity Code:
class MainProgramActivity : AppCompatActivity() {
lateinit var binding: ActivityMainProgramBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainProgramBinding.inflate(layoutInflater)
setContentView(binding.root)
replaceFragment(FragmentMainPage())
}
private fun replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentContainerView,fragment)
fragmentTransaction.commit()
}
}
My fragment code:
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
lateinit var binding: FragmentMainPageBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Toast.makeText(getActivity(),"Text!",Toast.LENGTH_SHORT).show();
binding = FragmentMainPageBinding.inflate(layoutInflater)
binding.aTextView.setText("Code working") //<-- I want this code to make changes towards the textView
return super.onCreateView(inflater, container, savedInstanceState)
}
}
The aTextView itself is empty at the beginning, the expected result will be the aTextView to show "Code working".
I see two problems with your code. First, exactly what Michael pointed out. You're returning the super method when you should be returning the View you just created (binding.root). Second, you're currenly leaking your fragment. When you viewbind a fragment, you are supposed to set the variable to null in onDestroyView(), as per defined in the documentation.
class FragmentMainPage : Fragment(R.layout.fragment_main_page) {
private var _binding: FragmentMainPageBinding? = null
private val binding get() = _binding!! // non-null variable in order to avoid having safe calls everywhere
// create the view through binding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMainPageBinding.inflate(layoutInflater, container, false)
return binding.root
}
// view already created, do whatever with it
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.aTextView.setText("Code working")
}
// clear the binding in order to avoid memory leaks
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

How to implement databinding with Reflection in BaseFragment()

I'm trying to implement a BaseFragment in which I will pass the layout resource on it and it should outputs the binding to work in the fragment itself instead of need to do it everytime the fragment is extended.
For example I have this BaseFragment
open class BaseFragment(#LayoutRes contentLayoutId : Int = 0) : Fragment(contentLayoutId) {
private lateinit var onInteractionListener: OnFragmentInteractionListener
val toolbar : Toolbar?
get() {
return if(activity is BaseActivity)
(activity as BaseActivity).toolbar
else
null
}
override fun onAttach(context: Context) {
super.onAttach(context)
setOnInteractionListener(context)
}
...
In which I use like this
class A(): BaseFragment(R.layout.myFragment) { ... }
Now, if I use this I will need to do the definition of the binding class again in my onCreateView
class A(): BaseFragment(R.layout.myFragment) {
private lateinit var binding: MyFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.myFragment, container, false)
return binding.root
}
override fun onDestroy(){
binding = null
}
}
What I want to implement is that since I'm passwing the layout to my BaseFragment, I want my BaseFragment to handle the creation of the binding and just return me the binding in the fragment which I use to extend BaseFragment
What I want to have is something like this
class A(): BaseFragment(R.layout.myFragment) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.myTextView = ""
}
}
So my question is, how I can implement inside BaseFragment the onDestroy() and the onCreateView to always create a binding for me from the layout I'm passing in ?
I heard that I should use reflection but I'm not that sure on how to accomplish it
I didn't hear about the possibility to get the databinding just from a layout, but even if it's possible, I don't think that is the recommended way, because of two reasons:
Reflection is slow
It makes things more complicated than they are.
Instead of making magic with Reflection, you could do something like this:
abstract class BaseFragment<out VB: ViewDataBinding>(
private val layout: Int,
// Other Dependencies if wanted
) : Fragment() {
abstract val viewModel: ViewModel
// other variables that all fragments need
// This does not cause any memory leak, because you are not storing the binding property.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = DataBindingUtil.inflate<VB>(inflater, layout, container, false).apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Do some basic work here that all fragments need
// like a progressbar that all fragments share, or a button, toolbar etc.
}
And then, when you still need the bindingProperty, I would suggest the following library (it handles all the onDestoryView stuff etc):
implementation 'com.kirich1409.viewbindingpropertydelegate:viewbindingpropertydelegate:1.2.2'
You can then use this like:
class YourFragment(yourLayout: Int) : BaseFragment<YourBindingClass>() {
private val yourBinding: YourBindingClass by viewBinding()
override val viewModel: YourViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// do binding stuff
}
}
Let me know if this worked for you.
Cheers

issue with fragment-to-fragment communication using viewModel

I'm new to Kotlin and android development but i can't find why my program isn't working.
I'm trying to be able to communicate from my first fragment to his child, and testing it with a string but it won't display.
Thanks in advance for your help !!!
My first fragment :
class FirstFragment : Fragment() {
private lateinit var viewModel : Communicator
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of supprimé
} ?: throw Exception("Invalid Activity")
viewModel.message.value = "test"
view.findViewById<Button>(R.id.button_stall_selection).setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
}
Here is my second :
class SecondFragment() : Fragment() {
private lateinit var viewModel :Communicator
private var msg: String? = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activity?.run {
ViewModelProvider(this).get(Communicator::class.java) // .of deleted
} ?: throw Exception("Invalid Activity")
viewModel.message.observe(viewLifecycleOwner, Observer {
msg = viewModel.message.value
})
view.findViewById<TextView>(R.id.textView_1).text = msg
view.findViewById<Button>(R.id.button_second).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
}
}
and finally here is the viewModel class i'm trying to use in order to communicate :
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class Communicator() : ViewModel(){
val message =MutableLiveData<String>()
fun setMsgCommunicator(msg:String){
message.setValue(msg)
}
}
In your FirstFragment, try to call viewModel.setMsgCommunicator("test") instead of directly calling viewModel.message.value = "test"

How to scope a Coroutine to a Fragment so it automatically cancels when Fragment is off screen or is destroyed?

I have this Fragment that just serves as a splash screen while data is retrieved. The problem is that on a configuration change or if the Fragment is offscreen (user navigated out of the app) it crashes when it returns from the IO Coroutine block and tries to execute the navigation in the Main Coroutine block.
Here is the code:
Note: viewModel.repository.initData() makes a Retrofit call and persists the response to a Room database if data doesn't exist or is stale.
class LoadingFragment : Fragment() {
private lateinit var viewModel: LoadingViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_loading, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(LoadingViewModel::class.java)
CoroutineScope(Dispatchers.IO).launch {
// Small delay so the user can actually see the splash screen
// for a moment as feedback of an attempt to retrieve data.
delay(250)
try {
viewModel.repository.initData()
CoroutineScope(Dispatchers.Main).launch {
findNavController().navigate(R.id.action_loadingFragment_to_mainFragment)
}
} catch (e: IOException) {
findNavController().navigate(R.id.action_loadingFragment_to_errorFragment)
}
}
}
}
Also I need the navigation to take place only after the data is retrieved but the data retrieval has to be done on the IO thread and the navigation on the main thread.
I have been reading about scoping the Coroutine but I am still confused/unsure how it works and how to properly set it up.
I was able to fix it by implementing something like this:
class LoadingFragment : Fragment() {
private lateinit var viewModel: LoadingViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_loading, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(LoadingViewModel::class.java)
lifecycleScope.launch {
withContext(Dispatchers.IO) {
// Small delay so the user can actually see the splash screen
// for a moment as feedback of an attempt to retrieve data.
delay(250)
try {
viewModel.initData()
withContext(Dispatchers.Main) {
findNavController().navigate(R.id.action_loadingFragment_to_mainFragment)
}
} catch (e: IOException) {
findNavController().navigate(R.id.action_loadingFragment_to_errorFragment)
}
}
}
}
}

Categories

Resources