The Android official page says to create the preferences Activity this way:
class MySettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, MySettingsFragment())
.commit()
}
}
When I do that, R.id.settings_container cannot be resolved. Should I simply use the automatic option "Create id value resource 'settings_container'", or that resource should be created somehow else to be meaningful?
use android.R.id.content (which is used in onCreate() also)
Related
I create ViewModel class at fragment, but viewModel is not saving after rotation - every time i got new Viewmodel instance. Where is problem?
Acrivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
Fragment:
class VideoFragment: Fragment() {
lateinit var viewModel: VideoViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProvider(this).get(VideoViewModel::class.java)
return inflater.inflate(R.layout.fragment_video, container, false)
}
}
ViewModel:
class VideoViewModel: ViewModel() {
init {
Log.i("XXX", "$this ")
}
}
if i will use "requireActivity()" - as ViewModelStoreOwner - viewModel isnt recreate, but its will bound to activity lifecycle.
viewModel = ViewModelProvider(requireActivity()).get(VideoViewModel::class.java)
This is because you are replacing your Fragment on every configuration change when the Activity is recreated. The FragmentManager already retains your Fragment for you. You should commit the transaction only on the initial creation:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
}
Your problem is most likely caused due to the activity being destroyed and then created again after app is rotated.
To fix this you can give your fragment an id/tag when navigating and then when activity is rotated call your supportFragmentManager if there already exists an instance of your old fragment, if it does, navigate to old fragment instance, otherwise create a new instance just like you are doing now.
Wax911 (commented on 9 may 2020) answer to this question:
https://github.com/InsertKoinIO/koin/issues/693
He explains the problem with activities and their lifecycle when rotating the screen
I have an Activity that extends ComponentActivity (Activity variant that is used for Compose based Activity Implementation), thus have no access to FragmentManager.
Is there any way to show DialogFragment(implemented with View System) in it ?
class MyActivity : ComponentActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//how to show DialogFragment here? there is no access to FragmentManager
ScreenContent()
}
}
}
I reckon, you must use FragmentActivity instead of ComponentActivity. It is already extended from ComponentActivity and supported for fragment manager. I have used it and it worked.
For instance;
class MyActivity : FragmentActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val dialogFragment = CustomDialogFragment.newInstance() // DialogFragment
dialogFragment.show(supportFragmentManager, "Your classname")
ScreenContent()
}
}
}
I'm relatively new to Android / Kotlin.
I wonder how Android libraries like AdMob manage to create and show a View (especially an Intersitial Ad) from inside a library without any layout preparation of the integrating app. I assume this View is some sort of Fragment.
Sample code to show an intersitial ad from AdMob:
I think it somehow has to do with the Activity passed as a parameter in the show method.
if (mInterstitialAd != null) {
mInterstitialAd?.show(this)
} else {
Log.d("TAG", "The interstitial ad wasn't ready yet.")
}
This Guide states, that to add a fragment programmatically, "the layout should include a FragmentContainerView". Additionally in the sample code from the same guide the id of said FragmentContainerView is used to add the fragment. This id is not known inside the library.
class ExampleActivity : AppCompatActivity(R.layout.example_activity) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<ExampleFragment>(R.id.fragment_container_view)
}
}
}
}
How does such a library achieve this?
I wonder how Android libraries like AdMob manage to create and show a View (especially an Intersitial Ad) from inside a library without any layout preparation of the integrating app.
What "layout preparations" would the integrating app need to do? Given an Activity, which you pass to the method, any code can use Activity.startActivity to launch it's own Activity which can be styled / themed in any way with any layout the library chooses (such as showing an interstitial ad).
I assume this View is some sort of Fragment.
Why would you assume that? It could be a Fragment, but it would be contained within an Activity, which could be launched as I've indicated above.
This Guide states, that to add a fragment programmatically, "the layout should include a FragmentContainerView". Additionally in the sample code from the same guide the id of said FragmentContainerView is used to add the fragment. This id is not known inside the library.
Right. But that again assumes tha the library is only using a Fragment and trying to shove it into your heirarchy. That's highly unlikely. It's more likely starting a brand new Activity that it knows about and has full control over.
I managed to achieve it.
A working demo can be found here: https://github.com/eat-your-broccoli/add-fragment-from-library-demo
Library:
class FragmentManager {
companion object {
fun showFragment(activity: AppCompatActivity) {
(activity.supportFragmentManager.findFragmentById(android.R.id.content) == null) {
activity.supportFragmentManager.beginTransaction()
.add(android.R.id.content, DemoFragment())
.addToBackStack(null)
.commit()
}
}
}
}
class DemoFragment : Fragment() {
private lateinit var btnBack: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_demo, container, false)
// enable back button
btnBack = view.findViewById(R.id.btn_back)
btnBack.setOnClickListener {
this.activity?.supportFragmentManager?.popBackStack()
}
return view
}
}
And in my activity:
class MainActivity : AppCompatActivity() {
lateinit var btnSwitch: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnSwitch = findViewById(R.id.btn_switch)
btnSwitch.setOnClickListener {
FragmentManager.showFragment(this)
}
}
}
A problem I had was that using R.id.content instead of android.R.id.content caused an execption and crashed the app.
I was trying to open some fragment however the app keep crashing
I am wondering if it was possible to fix the issue that I got
class HomePage : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home_page)
val addMed = findViewById<Button>(R.id.addMedButton)
val fragManager: FragmentManager = supportFragmentManager
fragManager.beginTransaction().add(R.id.frameLayout, addMed).commit()
}
}
You have to add a fragment, not a button.
Besides, if the button is found by id, it is already present in the activity.
Guess I'm missing something obvious here but... I'm storing data in uiModel in the DiaryViewModel class, and since I use architecture components I'm expecting the data to be retained through screen rotation - but it doesn't. I'm blind to why.
Here's a stripped down fragment
class DiaryFragment: Fragment() {
private lateinit var viewModel: DiaryViewModel
override onCreateView(...) {
viewModel = ViewModelProviders.of(this).get(DiaryViewModel::class.java)
viewModel.getModel().observe(this, Observer<DiaryUIModel> { uiModel ->
render(uiModel)
})
}
}
And the corresponding view model.
class DiaryViewModel: ViewModel() {
private var uiModel: MutableLiveData<DiaryUIModel>? = null
fun getModel(): LiveData<DiaryUIModel> {
if (uiModel == null) {
uiModel = MutableLiveData<DiaryUIModel>()
uiModel?.value = DiaryUIModel()
}
return uiModel as MutableLiveData<DiaryUIModel>
}
}
Can any one see what's missing in this simple example? Right now, uiModel is set to null when rotating the screen.
The issue was with how the activity was handling the fragment creation. MainActivity was always creating a new fragment per rotation, as in
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager
.beginTransaction()
.replace(overlay.id, DiaryFragment.newInstance())
.commit()
}
But of course, it works much better when checking if we have a saved instance, as in
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(overlay.id, DiaryFragment.newInstance())
.commit()
}
}