Im in a fragment1 and i want go to fragment2 if an event occurred in a class called from the fragment1. I have tried a callback of fuction: function in Class call a function in fragment1 to go in fragment but i collect this error:
Process: com.example.ilmiogioco, PID: 7992java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.savedstate.SavedStateRegistryController.performRestore(SavedStateRegistryController.java:61)
at androidx.navigation.NavBackStackEntry.<init>(NavBackStackEntry.java:88)
at androidx.navigation.NavBackStackEntry.<init>(NavBackStackEntry.java:73)
at androidx.navigation.NavController.navigate(NavController.java:1138)
at androidx.navigation.NavController.navigate(NavController.java:944)
at androidx.navigation.NavController.navigate(NavController.java:877)
at androidx.navigation.NavController.navigate(NavController.java:863)
at androidx.navigation.NavController.navigate(NavController.java:851)
at com.example.ilmiogioco.FullscreenFragmentSolo.follow(FullscreenFragmentSolo.kt:77)
at com.example.ilmiogioco.Solo.SpaceView.update(SpaceView.kt:276)
at com.example.ilmiogioco.Solo.SpaceView.run(SpaceView.kt:120)
at java.lang.Thread.run(Thread.java:919)
EDIT: I have fullscreenfragmentsolo (fragment1) that want in gameoverfragment (fragment2) if the class spaceview called in fullscreenfragmentsolo collect a lost game. The function follow() is called by spaceview for return in fullscreenfragmentsolo (maybe this is the thread error).
class FullscreenFragmentSolo : Fragment() {
private var spaceView: SpaceView? = null
private lateinit var backgroundMusic: MediaPlayer
private lateinit var window: Window
private var binding: FragmentFullscreenSoloBinding? = null
object size{
var x = 0
var y = 0
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
getActivity()?.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
val soundEffects = SoundEffects(requireContext())
soundEffects.playSound(SoundEffects.backgroundMusic)
val outMetrics = DisplayMetrics()
getActivity()?.getWindow()?.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window = activity?.getWindow()!!
window.attributes.width
window.attributes.height
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = activity?.display
display?.getRealMetrics(outMetrics)
} else {
#Suppress("DEPRECATION")
val display = activity?.windowManager?.defaultDisplay
#Suppress("DEPRECATION")
display?.getMetrics(outMetrics)
}
size.y = outMetrics.heightPixels
size.x = outMetrics.widthPixels
backgroundMusic = MediaPlayer.create(requireContext(), R.raw.background_music)
backgroundMusic.isLooping = true
backgroundMusic.start()
val fragmentBinding = FragmentFullscreenSoloBinding.inflate(inflater, container, false)
binding = fragmentBinding
fragmentBinding.root
spaceView = SpaceView(requireContext(), size, this)
return spaceView
}
fun follow(){
findNavController().navigate(R.id.action_fullscreenFragmentSolo_to_gameoverFragment)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.soloFragment = this
}
override fun onResume() {
super.onResume()
spaceView?.resume()
}
fun stopFunction() {
spaceView?.stop()
}
override fun onPause() {
super.onPause()
backgroundMusic.release()
spaceView?.pause()
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
GameoverFragment:
open class GameoverFragment : Fragment() {
private var binding: GameoverFragmentBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val fragmentBinding = GameoverFragmentBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.gameoverFragment = this
}
fun Menu(){
findNavController().navigate(R.id.action_gameoverFragment_to_startFragment)
}
> override fun onDestroyView() {
> super.onDestroyView()
> binding = null }
Can you help me?
This exception is due to your code calling (through navigation) the LifecycleRegistry.addObserver from a thread other than the Main thread. You have to ensure that you call the navigation from the main thread.
Change to this in the follow() function
import android.os.Handler
import android.os.Looper
// ...
fun follow() {
Handler(Looper.getMainLooper()).post {
findNavController().navigate(R.id.action_fullscreenFragmentSolo_to_gameoverFragment)
}
}
Related
Can someone help me figure out how i am supposed to navigate from an item in recyclerview to this repoFragment? I want to display these things in repoFragment using the GitHub API.
I want to click the reponame in the item of the recycler view. With the onClickListener i wish to open a new fragment (RepoFragment) with the info of the clicked item from the recyclerview.
I'm doing a repository search and want to display the info when clicking on the repository.The code below represents what i tried to do.
Thank you for your help!
This is my RepositoryRecyclerAdapter:
class RepositoryRecyclerAdapter(repos: List<RepositoriesResponse>): RecyclerView.Adapter<SearchPlaceHolder>(){
private var repoList = repos
var navc: NavController?= null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchPlaceHolder {
val inflatedView = LayoutInflater.from(parent.getContext()).inflate(R.layout.pravokutnik, parent, false);
return SearchPlaceHolder(inflatedView)
}
override fun onBindViewHolder(holder: SearchPlaceHolder, position: Int) {
val repo = repoList[position]
holder.bindRepo(repo)
holder.itemView.setOnClickListener(object: View.OnClickListener{
override fun onClick(v: View){
navc?.navigate(R.id.action_searchFragment_to_repoFragment)
}
})
}
override fun getItemCount() = repoList.size
}
This is my SearchFragment:
class SearchFragment : BaseFragment<SearchViewModel, FragmentSearchBinding, SearchRepository>() {
private lateinit var mContext: Context
private var reposList = ArrayList<RepositoriesResponse>().toList()
private lateinit var adapterRepo: RepositoryRecyclerAdapter
private lateinit var linearLayoutManagerDistance: LinearLayoutManager
val args: SearchFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mContext = this.requireContext()
linearLayoutManagerDistance = LinearLayoutManager(mContext)
linearLayoutManagerDistance.orientation = LinearLayoutManager.VERTICAL
binding.repositoriesList.layoutManager = linearLayoutManagerDistance
val query = args.query
val apiInterface = RepositoryApi.create().getRepositoriesByQuery(query)
apiInterface.enqueue( object : Callback<QueryResponse> {
override fun onResponse(call: Call<QueryResponse>?, response: Response<QueryResponse>?) {
if(response?.body() != null){
reposList = response.body()!!.items
adapterRepo = RepositoryRecyclerAdapter(reposList)
binding.repositoriesList.adapter = adapterRepo
}
}
override fun onFailure(call: Call<QueryResponse>?, t: Throwable?) {
Toast.makeText(mContext, t.toString(), Toast.LENGTH_LONG).show()
}
})
}
override fun getViewModel() = SearchViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentSearchBinding.inflate(inflater, container, false)
override fun getFragmentRepository()=
SearchRepository()
}
The HomeFragment:
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding, SearchRepository>() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var mySearchView = view.findViewById<SearchView>(R.id.search_view)
var navc = Navigation.findNavController(view)
mySearchView.setIconifiedByDefault(true);
mySearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
val action = HomeFragmentDirections.actionHomeFragmentToSearchFragment2(query)
navc?.navigate(action)
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
}
override fun getViewModel() = HomeViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentHomeBinding.inflate(inflater, container, false)
override fun getFragmentRepository()=
SearchRepository()
}
RepoFragment:
class RepoFragment : BaseFragment<RepoViewModel, FragmentRepoBinding, SearchRepository>(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_repo, container, false)
}
override fun getViewModel() = RepoViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentRepoBinding.inflate(inflater, container, false)
override fun getFragmentRepository() = SearchRepository()
}
Also, I get an error when trying to run this code; androidstudio says that i made a mistake when inflating the layout of repofragment.
Anytime my MainActivity restarts because of changing of dark mode and i try to click i get nullpointer exception.
The error is below
I have a BaseFragment
lateinit var mContext:Context
lateinit var mActivity:Activity
var myId = ""
val firebaseMethods: FirebaseMethods by lazy { FirebaseMethods(requireActivity()) }
override fun onDestroy() {
super.onDestroy()
removeListeners()
}
override fun onAttach(context: Context) {
super.onAttach(context)
showLogI("onAttach")
}
override fun onDetach() {
super.onDetach()
showLogI("onDetach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mContext = requireContext()
mActivity = requireActivity()
showLogI("onCreate")
if(getCurrentUser() != null){
myId = getCurrentUserId()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?)
: View {
showLogI("onCreateView")
val view = setContentView(inflater, container, savedInstanceState)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView(view)
setListener()
initData()
}
protected abstract fun setContentView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?):View
protected abstract fun setListener()
protected abstract fun initData()
I also have a PostFragment
var posts:ArrayList<Post> = arrayListOf()
var pgPosts:ArrayList<Post> = arrayListOf()
var ids:ArrayList<String> = arrayListOf()
val keys:ArrayList<String> = arrayListOf()
var totalSize = 0
var query: Query? = null
var postAdapter:PostAdapter? = null
var counter = 0
var adUnit = ""
var adminId = ""
override fun onDestroy() {
super.onDestroy()
postAdapter?.removeListeners()
destroyAds()
}
private var _binding: FragmentPostBinding? = null
private val binding get() = _binding!!
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun setContentView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentPostBinding.inflate(inflater, container, false)
return binding.root
}
override fun setListener() {
}
override fun initData() {
getArgs()
toggleOrientation()
showProgress()
setAdapter()
val spannableString = "No Internet Connection. Retry".createSpan("Retry"){ spannableString, start, end ->
mContext.setSpan(spannableString, "click", start, end, R.color.green){readPosts()}
}
if (mContext.isConnectingToInternet()) readPosts() else hideProgressWithSpan(spannableString)
adUnit = mContext.getString(R.string.ads_unit_video)
firebaseMethods.getAdmin { admin->
adminId = admin.admin_id
//adUnit = admin.native_ads_unit
}
}
fun changeLayout(orientation:String){
this.orientation = orientation
toggleOrientation()
setAdapter()
}
fun toggleOrientation(){
if (orientation == "big") {
max = 5
binding.recyclerview.lm()
} else if (orientation == "small") {
max = 15
binding.recyclerview.lm("grid", 3)
}
}
I get this null pointer exception
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.wh, PID: 26862
java.lang.NullPointerException at com.wh.ui.fragment.post.PostFragment.getBinding(PostFragment.kt:101)
at com.wh.ui.fragment.post.PostFragment.toggleOrientation(PostFragment.kt:160)
at com.wh.ui.fragment.post.PostFragment.changeLayout(PostFragment.kt:154)
As #Tenfour04 has said , you're accessing the binding when the fragment has been detached. You need to first check if the fragment has been added like so
if(isAdded){
//do something with the binding
}
Thanks a lot. I finally found the solution. I was checking if state is null for my bottom navigation view fragments and if null using new fragment, if not finding fragmentbytag. I just guessed to create the fragment without checking state and it worked.
I have a category item in my recyclerView.
There is a TextView and two ImageView as button (Edit button and Delete button).
When I click edit button I want to change TextView to EditText and editbutton change for agreebutton. When I write new text just update my old one.
I show you what I have and almost everything working but don't update my new text and I know code don't look nice and maybe someone can show me how to do it better :)
class CategoryAdapter(private val categoryList: List<Category>, val listener: ClickListener) : RecyclerView.Adapter<CategoryAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(CategoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.categoryName.text = categoryList[position].name
holder.binding.categoryEditIv.setOnClickListener {
holder.binding.categoryName.visibility = View.GONE
holder.binding.categoryEditName.visibility = View.VISIBLE
holder.binding.categoryEditName.setText(categoryList[position].name)
holder.binding.categoryEditIv.visibility = View.GONE
holder.binding.categoryAgreeIv.visibility = View.VISIBLE
}
val editTxt = holder.binding.categoryEditName
holder.binding.categoryAgreeIv.setOnClickListener {
val edit = editTxt.text.toString()
listener.editAgreeClickItem(edit)
holder.binding.categoryName.visibility = View.VISIBLE
holder.binding.categoryEditName.visibility = View.GONE
holder.binding.categoryAgreeIv.visibility = View.GONE
holder.binding.categoryEditIv.visibility = View.VISIBLE
}
holder.binding.categoryDeleteIv.setOnClickListener {
listener.deleteClickItem(categoryList[position])
}
}
override fun getItemCount(): Int {
return categoryList.size
}
class MyViewHolder(val binding: CategoryItemBinding) : RecyclerView.ViewHolder(binding.root)
interface ClickListener {
fun editAgreeClickItem(text: String)
fun deleteClickItem(category: Category)
}
}
class MainFragment : Fragment(), NewCategory.NewCategoryCreateListener, CategoryAdapter.ClickListener {
private var _binding: FragmentMainBinding? = null
private val binding
get() = _binding!!
private lateinit var shoppingListViewModel: ShoppingListViewModel
private lateinit var categoryAdapter: CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shoppingListViewModel = ViewModelProvider(requireActivity())[ShoppingListViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(layoutInflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.categoryRV.layoutManager = LinearLayoutManager(requireContext())
shoppingListViewModel.allCategories.observe(viewLifecycleOwner, {
updateCategories(it)
})
binding.addCategory.setOnClickListener {
val newCategory = NewCategory(this)
newCategory.show(childFragmentManager, "NewCategory")
}
}
private fun updateCategories(list: List<Category>) {
if(list.size == 0){
binding.noResult.visibility = View.VISIBLE
binding.categoryRV.visibility = View.GONE
}else{
binding.noResult.visibility = View.GONE
binding.categoryRV.visibility = View.VISIBLE
categoryAdapter = CategoryAdapter(list, this)
binding.categoryRV.adapter = categoryAdapter
}
}
override fun newCategoryCreate(text: String) {
val newCat = Category(text)
shoppingListViewModel.insertCategory(newCat)
}
override fun editAgreeClickItem(text: String) {
val newText = Category(text)
shoppingListViewModel.updateCategory(newText)
}
override fun deleteClickItem(category: Category) {
shoppingListViewModel.deleteCategory(category)
}
}
I am very new to taking an android application and for a project I need a BottomNavigati but I cannot get my data that comes from the beginning of the session to pass to the main fragment
Activity:
val bundle1 = Bundle()
bundle1.putString("User",User)
bundle1.putString("Correo",Correo)
bundle1.putString("Region",Region)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController: NavController = navHostFragment.navController
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
)
)
navHostFragment.arguments = bundle1
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
HOMEFRAGMEN:
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.textView4)
return root
}
}
There is no direct access to HomeFragment from MainActivity in your case. BottomNavigation uses nested navigation architecture pattern with nested graphs.
More details here: Passing argument(s) to a nested Navigation architecture component graph
But you can use alternative way to pass data from Activity to Fragment using context.
Step 1 - Describe HomeFragmentData model:
data class HomeFragmentData(
val value: String
)
Step 2 - Describe HomeFragment interface:
interface IHomeFragment {
fun getHomeFragmentData(): HomeFragmentData
}
Step 3 - Implement interface to your Activity:
class MainActivity : AppCompatActivity(), IHomeFragment {
override fun getHomeFragmentData(): HomeFragmentData {
return HomeFragmentData(
value = "Home Fragment data"
)
}
// Rest of your code
}
Step 4 - Call function getHomeFragmentData() from HomeFragment using context:
class HomeFragment : Fragment() {
private var interactionListener: IHomeFragment? = null
override fun onAttach(context: Context) {
super.onAttach(context)
when(context) {
is IHomeFragment -> {
interactionListener = context
}
else -> throw RuntimeException("$context has to implement IHomeFragment")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
interactionListener?.getHomeFragmentData().let {
textView.text = it?.value
}
return root
}
}
I'm having a problem with this SmartToolbar, if I open the app and click on the chat or book icon it opens perfectly and shows the fragments, but if I click on another icon to open another fragment, when I go back to them they don't show more The fragments that should be on the smartToolbar, as shown in the prints below, bug everything! I need to help on this as soon as possible !!!
[Opens normally When I click on another item and go back to it, it no longer opens the SmartToolbar fragmentshere]2
enter image description here
I will send the homeAcitivity code, where is the layout that opens the fragments of the buttonNavigationBar
class HomeActivity : AppCompatActivity() {
var MY_ACCOUNT = 1
var LIBRARY = 2
var HOME = 3
var CHAT = 4
var SETTINGS = 5
private var Content: ConstraintLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar!!.hide()
nav.show(HOME)
nav.setBackgroundColor(Color.TRANSPARENT)
abrirHome()
addOnMeowNavigattionBarIcons()
addOnNotificationInform()
chamarFragments()
}
fun abrirHome() {
val fragment = HomeFragment.newInstance()
addFragment(fragment)
}
fun addOnMeowNavigattionBarIcons() {
nav.add(MeowBottomNavigation.Model(MY_ACCOUNT, R.drawable.ic_baseline_account_circle_24))
nav.add(MeowBottomNavigation.Model(LIBRARY, R.drawable.ic_baseline_menu_book_24))
nav.add(MeowBottomNavigation.Model(HOME, R.drawable.ic_baseline_home_24))
nav.add(MeowBottomNavigation.Model(CHAT, R.drawable.ic_baseline_chat_24))
nav.add(MeowBottomNavigation.Model(SETTINGS, R.drawable.ic_baseline_settings_24))
}
fun addOnNotificationInform() {
nav.setCount(CHAT, "99")
}
fun chamarFragments() {
nav.setOnClickMenuListener {
when (it.id) {
HOME -> {
val fragment = HomeFragment.newInstance()
addFragment(fragment)
}
MY_ACCOUNT -> {
val fragment = MyAccountFragment.newInstance()
addFragment(fragment)
}
LIBRARY -> {
val fragment = LibraryFragment.newInstance()
addFragment(fragment)
}
CHAT -> {
val fragment = ChatFragment.newInstance()
addFragment(fragment)
}
SETTINGS -> {
val fragment = SettingsFragment.newInstance()
addFragment(fragment)
}
}
}
}
private fun addFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.parentfragments,fragment,fragment.javaClass.simpleName)
.addToBackStack(null)
.commit()
}
fun sair(view: View){
FirebaseAuth.getInstance().signOut()
val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
val intent = Intent(this, login::class.java)
startActivity(intent,bundle)
}
}
This is the code of the fragment Library that has a SmartToolbar that calls two more fragments within it, MyLibrary and Store
class LibraryFragment: Fragment() {
companion object{
fun newInstance():LibraryFragment {
val args = Bundle()
val fragment = LibraryFragment()
fragment.arguments = args
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.library_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = FragmentPagerItemAdapter(
fragmentManager, FragmentPagerItems.with(context)
.add("Minha Bilioteca", MyLibraryFragment::class.java)
.add("Loja", StoreFragment::class.java)
.create())
viewpager.adapter = adapter
viewpagertab.setViewPager(viewpager)
}
}
And this is the MyLibrary fragment that is shown inside the Library that is shown inside HomeActivity
class MyLibraryFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.mylibrary_fragment, container, false)
}
}