setTargetFragment() is deprecated in Java, and I don't understand the correct replacement for it as android documentation uses it and is outdated. I believe the FragmentManager is the correct replacement for it. I am using the deprecated setTargetFragment function in my settings Preferences to create multiple preference screens. To do so, I originally followed the guide here which confusingly uses setTargetFragment in the example. Below is my code:
build.gradle (Module: app)
...
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference:1.1.1'
...
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var settingsButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
...
settingsButton = findViewById(R.id.settingsButtonMain)
settingsButton.setOnClickListener {
settingsClicked()
}
...
}
private fun settingsClicked() {
val settingsIntent = Intent(this, SettingsActivity::class.java)
startActivity(settingsIntent)
}
}
SettingsActivity.kt
class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onCreate(savedInstanceState: Bundle?) {
...
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment(this)).commit()
supportFragmentManager.addOnBackStackChangedListener {
if(supportFragmentManager.backStackEntryCount == 0) {
title = "App Settings"
}
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
...
}
class SettingsFragment(cont: Context) : PreferenceFragmentCompat() {
private var context1: Context = cont
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
class Screen2PreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.screen2_preferences, null)
}
}
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference): Boolean {
val args: Bundle = pref.extras
val fragment: Fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
fragment.arguments = args
fragment.setTargetFragment(caller, 0) //TROUBLE AREA. WHAT IS THE CORRECT REPLACEMENT HERE?
supportFragmentManager.beginTransaction().replace(R.id.settings, fragment).addToBackStack(null).commit()
return true
}
override fun onSupportNavigateUp(): Boolean {
if(supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}
}
Include this dependency:
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
Use setFragmentResultListener instead of setTargetFragment():
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference): Boolean {
val args: Bundle = pref.extras
val fragment: Fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
fragment.arguments = args
supportFragmentManager.beginTransaction().replace(R.id.settings, fragment).addToBackStack(null).commit()
supportFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
if (key == "requestKey") {
// Get result from bundle
}
}
return true
}
And in your fragment that returns a result:
// Insert result in a bundle
setFragmentResult("requestKey", bundle)
For the unresolved issue when trying to use setFragmentResult referring to the answer above, you could try using the fragmentManager as follows.
This is for Xamarin:
ParentFragmentManager.SetFragmentResult("requestKey", bundle);
Related
I am making a simple app in kotlin and would like to add an additional option for night mode like this
example from Material Files app
I was thinking of adding a style and applying it programmatically, But I don't know how to do it properly.
Here's my code:
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
//It is called after super.onCreate because otherwise isNightMode does not return the correct value
when (ThemeHelper.nightModeChoice(this)) {
"nightModeFollowSystem" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"nightModeOn" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"nightModeOff" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
override fun onResume() {
super.onResume()
//If MainActivity is recreated return to SettingsFragment to have a nice animation
if (intent.extras != null) {
if (intent.extras!!.getBoolean("TEMA_CAMBIATO")) {
intent.putExtra("TEMA_CAMBIATO", false)
navController.navigate(R.id.SettingsFragment)
}
}
}
ThemeHelper,
this part is a bit messy, but it serves to prevent the activity from being recreated unnecessarily.
object ThemeHelper {
private fun isNightMode(context: Context): Boolean {
return context.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
fun nightModeChoice(context: Context): String? {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString("modTema", "nightModeFollowSystem")
}
fun recreate(context: Context, activity: Activity) {
var flag = true
if (isNightMode(context) && nightModeChoice(context) == "nightModeOn") {
Log.d("THEME_HELPER", "Non c'è bisogno di aggiornare")
flag = false
}
if (!isNightMode(context) && nightModeChoice(context) == "nightModeOff") {
Log.d("THEME_HELPER", "Non c'è bisogno di aggiornare")
flag = false
}
if (flag) {
val intent = activity.intent
intent?.putExtra("TEMA_CAMBIATO", true)
activity.finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
activity.startActivity(intent)
}
}
}
SettingsFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroyView() {
super.onDestroyView()
PreferenceManager.getDefaultSharedPreferences(requireContext()).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (isAdded) {
if (key == "modTema") {
ThemeHelper.recreate(requireContext(), requireActivity())
}
}
}
I solved in this way btw:
ThemeHelper
fun applyAmoled(context: Context, view: View){
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.getBoolean("modAmoled", false) && isNightMode(context)){
view.setBackgroundColor(Color.BLACK)
}
}
MainActivity after super.onCreate
ThemeHelper.applyAmoled(this, findViewById(R.id.content_main))
It's not the best solution but it works, I just need to recreate activity when needed
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?
I want to use a DropDownPreference for my settings page but despite looking all over the internet, there doesn't seem to be any decent tutorial on how to do this. Does anyone know what should go in the onPreferenceChange method? I previously used a RadioButton but now want to use a DropDownPreference for easier implementation and maintenance.
Activity
class SettingsActivity : AppCompatActivity(), FragmentSettings.PreferenceXchangeListener {
private var mCurrentValue: Boolean = false // False is the default value
override fun onCreate(savedInstanceState: Bundle?) {
val mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mCurrentValue = mSharedPreferences.getBoolean("preference_dark", false)
if (mCurrentValue) {
setTheme(R.style.MyDarkSettingsTheme)
} else {
setTheme(R.style.MyLightSettingsTheme)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val viewllSettingsContainer = settings_container
val root = viewllSettingsContainer.rootView
if (mCurrentValue) {
root.setBackgroundColor(Color.BLACK)
} else {
root.setBackgroundColor(Color.WHITE)
}
val settingsFragment = FragmentSettings()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, settingsFragment)
.commit()
}
override fun onXchange(value:Boolean) {
when {
mCurrentValue != value -> {
mCurrentValue = value
recreate()
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
val intent = parentActivityIntent
intent?.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
onBackPressed()
true
}
else ->
super.onOptionsItemSelected(item)
}
}
}
Fragment
class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_preferences)
}
}
I think you're over-complicating it. There is no need to involve the fragment in listening to changes to a preference it won't be handling and passing that back to the activity. You can register a preference change listener for all preferences in your Activity and respond accordingly there.
class SettingsActivity : AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
//...
}
override fun onDestroy() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
//...
super.onDestroy()
}
override fun onSharedPreferenceChanged(_: SharedPreferences, key: String) {
when (key){
"preference_dark" -> recreate()
}
}
}
I'm new in Kotlin Developement. I'm trying to start a new activity from the main activity but the only thing it displays is a blank message after the toast message. But I really don't understand where the problem comes from.
Here's MainActivity:
class MainActivity : AppCompatActivity() {
private fun replaceFragment(fragment: Fragment){
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, fragment)
fragmentTransaction.commit()
}
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener {
item -> when (item.itemId) {
R.id.nav_home -> {
//replaceFragment(HomeFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_search -> {
replaceFragment(SearchFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_person -> {
replaceFragment(ProfileFragment())
return#OnNavigationItemSelectedListener true
}
R.id.nav_favorites -> {
replaceFragment(FavoritesFragment())
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.bottom_navigation)
navView.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
navView.setSelectedItemId(R.id.nav_home)
if (!imgurClient.isConnected) {
Toast.makeText(applicationContext,"You are not connected.",Toast.LENGTH_SHORT).show()
val intentToWebView = Intent(this, LoginActivity::class.java)
startActivity(intentToWebView)
}
}
}
Here's LoginActivity:
eclass LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_login)
}
}
Activity_login.xml contains a textview which displays a text in white and the background is purple.
Can you help me please?
You have to override the correct onCreate method without the PersistableBundle as a second argument:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
}
You've overridden the wrong method in your LoginActivity. It should be:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_login)
}
}
You have to override the correct onCreate method. It should be
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
}
I am using fragments inside my main activity and I want to send an object of my custom class "TaskWithUserAndProfile" to the TaskDetailsFragment
I found out that you can do it with Bundle and made it send a string, but things got complicated when I tried to send with Parcebale.
here are some parts of my code for better understanding:
TaskWithUserAndProfile.kt
class TaskWithUserAndProfile() : Parcelable{
override fun writeToParcel(p0: Parcel?, p1: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
var profile = Profile()
var task = Task()
var user = User()
constructor(parcel: Parcel) : this() {
//profile = parcel.read
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TaskWithUserAndProfile> {
override fun createFromParcel(parcel: Parcel): TaskWithUserAndProfile {
return TaskWithUserAndProfile(parcel)
}
override fun newArray(size: Int): Array<TaskWithUserAndProfile?> {
return arrayOfNulls(size)
}
}
}
HomeFragment.kt
//Inside onCreateView
adapter = TasksAdapter(tasksArray) { item ->
println(item.profile)
val bundle = Bundle()
bundle.putParcelable("MyItem", item)
val taskDetailsFragment = TaskDetailsFragment()
taskDetailsFragment.arguments = bundle
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.container, taskDetailsFragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
How should my class that implements the Parcebale look like and how can I then send and receive the item object in fragments?
You don't need to use Parcelable even, just simply define an TaskWithUserAndProfile variable in your TaskDetailsFragment and set in in HomeFragment.
TaskDetailsFragment.kt
class TaskDetailsFragment : Fragment() {
var selectedTask: TaskWithUserAndProfile? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
}
}
HomeFragment.kt
//Inside onCreateView
adapter = TasksAdapter(tasksArray) { item ->
val taskDetailsFragment = TaskDetailsFragment()
taskDetailsFragment.selectedTask = item
val fragmentTransaction =
fragmentManager?.beginTransaction()
fragmentTransaction?.replace(R.id.container, taskDetailsFragment)
fragmentTransaction?.addToBackStack(null)
fragmentTransaction?.commit()
}
if you wanna keep using parcelize, just try this sample:
#Parcelize
data class TaskWithUserAndProfile(var profile:Profile, var task :Task, var user:User) : Parcelable{}
I could miss something from your class but the idea should looks like this, so use annotation #Parcelize and Parcelable implementation (do not need to override any method).
Update
Thanks for reminder. You will have to add this to your gradle file:
androidExtensions {
experimental = true
}
Use this plugin:
android-parcelable-intellij-plugin-kotlin
for TaskWithUserAndProfile, Profile, Task, User models.