End activity from fragment - android

I'm trying to implement an onboard activity which shows only for the first time of using the app,
after it finished it will move to the main activity.
I've made another activity which includes fragments,
afterwards I'd like to set a settings on shared-preferences which hold a value that tells if it first time or not.
onboard activity:
class OnboardActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_onboard)
}
fun finishOnBoarding() {
val preferences = getSharedPreferences("my_preferences", MODE_PRIVATE)
preferences.edit()
.putBoolean("onboarding_complete", true).apply()
val main = Intent(this, MainActivity::class.java)
startActivity(main)
finish()
}
}
InitialScreenFragment which is the last fragment in my code:
class InitialScreenFragment : Fragment() {
private var _binding: FragmentInitialScreenBinding? = null
private val binding get() = _binding!!
lateinit var callback: FragmentActivity
override fun onAttach(context: Context) {
super.onAttach(context)
callback = requireActivity();
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentInitialScreenBinding.inflate(inflater, container, false)
val view = binding.root
//finish button which suppose to end the session of boarding
// binding.finishBtn.setOnClickListener{
// callback.finishOnBoarding()
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
how can I set the listener on finish button to call my activity method to end the onboard please?
thanks

You may use separate interface as callback
interface OnboardingListener {
fun finishOnboarding()
}
implement this interface in your OnboardActivity
class OnboardActivity : AppCompatActivity(), OnboardingListener {
override fun finishOnboarding(){
val preferences = getSharedPreferences("my_preferences", MODE_PRIVATE)
preferences.edit().putBoolean("onboarding_complete", true).apply()
val main = Intent(this, MainActivity::class.java)
startActivity(main)
finish()
}
}
and for example set it to fragment from your activity
class InitialScreenFragment : Fragment() {
.....
var callback: OnboardingListener? = null
.....
}
val fragment: InitialScreenFragment = InitialScreenFragment()
fragment.callback = this // this should be in OnboardActivity where you create an instance of InitialScreenFragment
and then from your fragment call
callback?. finishOnboarding()

Related

Shared ViewModel Not Working With Bottom Sheet Dialog Fragment, DB and UI

i have a really simple vocabulary note app contains 2 fragment and 1 root activity. In HomeFragment i have a button "addVocabularyButton". When it is clicked a BottomSheetDialogFragment appears and user gives 3 inputs and with a viewmodel it is saved in DB. My problem is when i save the input to the DB it works fine but i cannot see in HomeFragment that word instantaneously. I have to re-run the app to see in home fragment. I am using Navigation library and recycler view in home fragment.
Github link : https://github.com/ugursnr/MyVocabularyNotebook
Home Fragment
class HomeFragment : Fragment() {
private var _binding : FragmentHomeBinding? = null
private val binding get() = _binding!!
private var vocabularyAdapter = VocabulariesHomeAdapter()
private lateinit var sharedViewModel: AddVocabularySharedViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(layoutInflater,container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
sharedViewModel.getAllVocabulariesFromDB()
observeAllVocabularies()
prepareRecyclerView()
addVocabularyOnClick()
vocabularyAdapter.onItemDeleteClicked = {
sharedViewModel.deleteVocabulary(it)
observeAllVocabularies()
}
}
private fun prepareRecyclerView(){
binding.recyclerViewHome.apply {
layoutManager = LinearLayoutManager(context)
adapter = vocabularyAdapter
}
}
private fun addVocabularyOnClick(){
binding.addVocabularyButton.setOnClickListener{
val action = HomeFragmentDirections.actionHomeFragmentToAddVocabularyBottomSheetFragment()
Navigation.findNavController(it).navigate(action)
}
}
private fun observeAllVocabularies(){
sharedViewModel.allVocabulariesLiveData.observe(viewLifecycleOwner, Observer {
vocabularyAdapter.updateVocabularyList(it)
})
}
}
Dialog Fragment
class AddVocabularyBottomSheetFragment : BottomSheetDialogFragment() {
private var _binding : FragmentAddVocabularyBottomSheetBinding? = null
private val binding get() = _binding!!
private lateinit var sharedViewModel: AddVocabularySharedViewModel
private var vocabularyInput : String? = null
private var translationInput : String? = null
private var sampleSentenceInput : String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAddVocabularyBottomSheetBinding.inflate(layoutInflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
binding.addOrUpdateVocabularyButton.setOnClickListener {
vocabularyInput = binding.vocabularyActualET.text.toString()
translationInput = binding.vocabularyTranslationET.text.toString()
sampleSentenceInput = binding.vocabularySampleSentenceET.text.toString()
val inputVocabulary = Vocabulary(vocabularyInput,translationInput,sampleSentenceInput)
insertVocabularyToDB(inputVocabulary)
sharedViewModel.getAllVocabulariesFromDB()
dismiss()
}
}
private fun insertVocabularyToDB(vocabulary: Vocabulary){
sharedViewModel.insertVocabulary(vocabulary)
}
}
Shared ViewModel
class AddVocabularySharedViewModel(application: Application) : AndroidViewModel(application) {
private var _allVocabulariesLiveData = MutableLiveData<List<Vocabulary>>()
private var _vocabularyLiveData = MutableLiveData<Vocabulary>()
val allVocabulariesLiveData get() = _allVocabulariesLiveData
val vocabularyLiveData get() = _vocabularyLiveData
val dao = VocabularyDatabase.makeDatabase(application).vocabularyDao()
val repository = VocabularyRepository(dao)
fun insertVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.insertVocabulary(vocabulary)
}
fun updateVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.updateVocabulary(vocabulary)
}
fun deleteVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.deleteVocabulary(vocabulary)
}
fun getAllVocabulariesFromDB() = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getAllVocabulariesFromDB()
withContext(Dispatchers.Main){
_allVocabulariesLiveData.value = temp
}
}
fun getVocabularyDetailsByID(vocabularyID : Int) = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getVocabularyDetailsByID(vocabularyID).first()
withContext(Dispatchers.Main){
_vocabularyLiveData.value = temp
}
}
}
Adapter
class VocabulariesHomeAdapter : RecyclerView.Adapter<VocabulariesHomeAdapter.VocabulariesHomeViewHolder>() {
lateinit var onItemDeleteClicked : ((Vocabulary) -> Unit)
val allVocabulariesList = arrayListOf<Vocabulary>()
class VocabulariesHomeViewHolder(val binding : RecyclerRowBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VocabulariesHomeViewHolder {
return VocabulariesHomeViewHolder(RecyclerRowBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: VocabulariesHomeViewHolder, position: Int) {
val vocabulary = allVocabulariesList[position]
holder.binding.apply {
actualWordTV.text = vocabulary.vocabulary
translationWordTV.text = vocabulary.vocabularyTranslation
deleteButtonRV.setOnClickListener {
onItemDeleteClicked.invoke(vocabulary)
notifyItemRemoved(position)
}
}
}
override fun getItemCount(): Int {
return allVocabulariesList.size
}
fun updateVocabularyList(newList : List<Vocabulary>){
allVocabulariesList.clear()
allVocabulariesList.addAll(newList)
notifyDataSetChanged()
}
}
I know there are lots of codes up there but i have a really big problems about using these dialog fragments. Thank you for your help.
This is because multiple instances of the same View Model are created by the Navigation Library for each Navigation Screen.
You need to tell the Navigation Library to share the same ViewModel between all navigation screens.
Easiest way to fix this is to scope the viewModel to the Activity rather than a Fragment and using it in all your fragments.
val viewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
This way, the viewModel is scoped to the Application instance rather than Fragment. This will keep the state in the viewModel persistent across the Application.
You can also do this by scoping the viewModel to the navigation graph.
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
Alternate method, if you're using dependency injection libraries
val navController = findNavController();
val navBackStackEntry = navController.currentBackStackEntry!!
If you use hilt, you can just pass your NavBackStackEntry of the NavGraph to hiltViewModel()
val viewModel = hiltViewModel<MyViewModel>(//pass NavBackStackEntry)
This will give you a viewModel that is scoped to NavBackStackEntry and will only be recreated when you pop the NavBackStackEntry(ie Navigate out of the navigation screens.)

RegisterForActivityResult callback is not triggered after changing Fragment Android

Fist of all, I am using bottomNavigation which has five fragments on the MainAcitvity .
The problem is down below.
I am using RegisterForActivityResult for gallery image in the fragmentA.
when I use that before changing to other fragments, It's perfectly fine.
However, after changing to different Fragments
and back to the fragmentA then call RegisterForActivityResult again, the callback is not triggered.
when my MainActivity is with in Condition1 I had a problem.
When my MainActivity is with in Condition2 RegisterForActivityResult callback was fine.
condition1----------------------------------------------------------------------
I Initialized fragment variables in the onCreate on MainActivity , and BottomNavigationListener as well.
Condition2----------------------------------------------------------------------
I Initialized fragment variables in the onStart on MainActivity , and BottomNavigationListener as well.
Does anybody know why?
** Problem Code ↓ condition1**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
** Working Code ↓ condition2**
class Main : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener {
//viewbinding
private lateinit var binding: Activity4MainBinding
//fragment Verables
private lateinit var homeFragment :Fragment_Home
private lateinit var liveFragment :Fragment_Live
private lateinit var boardFragment :Fragment_Board
private lateinit var chatFragment :Fragment_Chat
private lateinit var mypageFragment :Fragment_Mypage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity4MainBinding.inflate(layoutInflater)
setContentView(binding.root)
homeFragment = Fragment_Home.newInstance()
//first fragment when the App started
supportFragmentManager.beginTransaction().add(R.id.main_layout,
homeFragment).commitAllowingStateLoss()
}
override fun onStart() {
super.onStart()
liveFragment = Fragment_Live.newInstance()
boardFragment = Fragment_Board.newInstance()
chatFragment = Fragment_Chat.newInstance()
mypageFragment = Fragment_Mypage.newInstance()
binding.bottomNavigationView.setOnNavigationItemSelectedListener(this)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, homeFragment).commit()
}
R.id.live -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, liveFragment).commit()
}
R.id.board -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, boardFragment).commit()
}
R.id.chat -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, chatFragment).commit()
}
R.id.mypage -> {
supportFragmentManager.beginTransaction().replace(R.id.main_layout, mypageFragment).commit()
}
}
return true
}
}
FragmentA code↓
class FragmentA : Fragment() {
// binding
lateinit var binding:FragmentABinding
//for instance from outside of the fragment
fun newInstance() : FragmentA{
return FragmentA()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = FragmentABinding.inflate(inflater, container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)
binding.button.setOnClickListener{
requestImagesFromGallery.launch("image/*")}
}
private val
RequestIamgeFromGallery=registerForActivityResult(ActivityResultContracts.GetContent()) {
//If callback is triggered, returned uri set into the Imageview
binding.mypageImage.setImageURI(uri)
}
}
Please give me some comments why those Fragments instance in the MainActivity onStart work, but onCreate is not working. Also is it ok if I use the condition2?

How to start a service and then bind to it from a fragment

I have a TimerService that I'd like to bind to with a TimerFragment, so I can call the Service's methods and observe its LiveData in the fragment. The problem I'm running into is that when I start my fragment, it's telling me that the lateinit var timerService hasn't been initialized by the time I try to use it in onStart. I thought I was starting the service and binding to it by that time in the fragment lifecycle, so I'm not sure what's causing the issue.
My fragment code is as follows:
class TimerFragment : Fragment() {
private lateinit var scaleUp: ObjectAnimator
private lateinit var alphaDown: ObjectAnimator
private lateinit var timerService: TimerService
private var bound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as TimerService.LocalBinder
timerService = binder.getService()
bound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
bound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
requireActivity().startService(Intent(requireContext(), TimerService::class.java))
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_timer, container, false)
}
override fun onStart() {
super.onStart()
Intent(requireContext(), TimerService::class.java).also{ intent ->
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
timerService.secondsElapsed.observe(viewLifecycleOwner, Observer {
updateTimerUI(it)
})
timerService.timerState.observe(viewLifecycleOwner, Observer {
updateButtons(it)
})
}
override fun onStop() {
super.onStop()
requireActivity().unbindService(connection)
bound = false
}
// other stuff
}

Passing a value from Activity to Fragment in Kotlin

I created a bottom navigation activity in my project, which contains one activity and two fragments. In Main Activity I have value stored in a variable but if I pass the value to the fragments then I am getting NullPointer Exception error. I am using kotlin in my project and any help is appreciated.
Expectation
Get Value into Fragment from MainActivity. MainActivity--->TestOneFragment
Language Used
Kotlin
Main Activity
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener
{
private val KEY_POSITION = "keyPosition"
private var navPosition: BottomNavigationPosition = BottomNavigationPosition.ONE
private lateinit var toolbar: Toolbar
private lateinit var bottomNavigation: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
restoreSaveInstanceState(savedInstanceState)
setContentView(R.layout.activity_test)
toolbar = findViewById(R.id.toolbar)
bottomNavigation = findViewById(R.id.bottom_navigation)
setSupportActionBar(toolbar)
initBottomNavigation()
initFragment(savedInstanceState)
var Name:String=intent.getStringExtra("name")
println("Test CLLicked: $Name")
//This code is to pass the value to Fragment
var bundle=Bundle()
bundle.putString("name",Name)
var frag=TestFragment()
frag.arguments=bundle
}
override fun onSaveInstanceState(outState: Bundle?)
{
outState?.putInt(KEY_POSITION, navPosition.id)
super.onSaveInstanceState(outState)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean
{
navPosition = findNavigationPositionById(item.itemId)
return switchFragment(navPosition)
}
private fun restoreSaveInstanceState(savedInstanceState: Bundle?)
{
savedInstanceState?.also {
val id = it.getInt(KEY_POSITION, BottomNavigationPosition.ONE.id)
navPosition = findNavigationPositionById(id)
}
}
private fun initBottomNavigation()
{
bottomNavigation.active(navPosition.position)
bottomNavigation.setOnNavigationItemSelectedListener(this)
}
private fun initFragment(savedInstanceState: Bundle?)
{
savedInstanceState ?: switchFragment(BottomNavigationPosition.ONE)
private fun switchFragment(navPosition: BottomNavigationPosition): Boolean {
return supportFragmentManager.findFragment(navPosition).let {
if (it.isAdded) return false
supportFragmentManager.detach() // Extension function
supportFragmentManager.attach(it, navPosition.getTag()) // Extension function
supportFragmentManager.executePendingTransactions()
}
}
private fun FragmentManager.findFragment(position: BottomNavigationPosition): Fragment
{
return findFragmentByTag(position.getTag()) ?: position.createFragment()
}
}
TestOneFragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val testName= arguments!!.getString("name")
....
}
Error
kotlin.KotlinNullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
Here is an example of the newInstance pattern for creating Fragments.
This is within a companion object, which is pretty much just a way to say "these things are Static."
First, you should define constants for your Bundle names, this will help keep everything aligned. Next, define a newInstance method that takes your parameters, such as the name.
And within there, you will create your Fragment and return it. This way, your Activity doesn't have to worry about the Bundle or anything. All your logic is within one place, for storing/retrieving, all within your Fragment.
class TestOneFragment {
companion object {
const val ARG_NAME = "name"
fun newInstance(name: String): TestOneFragment {
val fragment = TestOneFragment()
val bundle = Bundle().apply {
putString(ARG_NAME, name)
}
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val name = arguments?.getString(ARG_NAME)
// ...
}
}
And now, you can easily get your Fragment by doing the following.
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
// ...
val name = intent.getStringExtra("name")
// Creating the new Fragment with the name passed in.
val fragment = TestFragment.newInstance(name)
}
}
Hopefully that helps!

Fragments and Activities using Kotlin

I have the following activity with two integers
class ComplexActivity : AppCompatActivity() {
var clubs : Int = 0
var diamonds : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_complex)
val fragment = ClubsFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.main_frame, fragment)
transaction.commit()
}
}
I want to change the value of the integer clubs from the fragment ClubsFragment when isScored is true
class ClubsFragment : Fragment(), SeekBar.OnSeekBarChangeListener{
private var isScored = false
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val v = inflater!!.inflate(R.layout.fragment_clubs, container, false)
v.image_clubs.setOnClickListener {
if(isScored){
activity.clubs = 4
}
}
}
}
I tried to use activity.clubs but It's not working. How can I access the activity constants from a fragment.
You would create an interface, let's say FragmentListener for your Activity that contains a function like fun updateClubs(count: Int). Your Activity should implement this interface.
Then, in your Fragment, add a fragmentListener property and override onAttach(context: Context):
private var fragmentListener: FragmentListener? = null
override fun onAttach(context: Context) {
this.listener = context as? FragmentListener
}
Then, in your OnClickListener, you can simply call fragmentListener?.updateClubs(4).

Categories

Resources