I’ve been trying to put a simple app together, using Compose 1.0.0-beta09, Kotlin 1.5.10, and Jetpack Navigation 2.3.4.
The app has one activity and two fragments.
First (main) fragment /screen (clicking in the button takes me to the second fragment/screen ): Screen One screenshot
Second fragment:/screen: Screen Two screenshot
Problem: After interacting with (putting the cursor in) the TextField on the first screen and subsequently clicking on the button, the second screen loads but is empty (the onCreateView of the SecondFragment is called but the setContent doesn’t work / the screen doesn’t get recomposed?).
If I don’t interact with the TextField, the problem doesn’t happen.
I’ve tested on emulators with API levels 28 & 30, compose 1.0.0-beta0709, Kotlin 1.4.32 & 1.5.10 with similar results.
Empty Screen Two
Main classes:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AndroidViewBinding(ContentMainBinding::inflate)
}
}
}
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = ComposeView(inflater.context).apply {
setContent {
FirstScreen( onButtonClick = {
findNavController().navigate(R.id.nav_second_fragment)
})
}
}
}
#Composable
fun FirstScreen(onButtonClick: () -> Unit) {
Column {
Text("Screen One", color = Color.Blue, fontSize = 30.sp)
Button(
onClick = {
onButtonClick() },
content = {
Text(text = "go to Screen Two", color = Color.White)
})
TextField(
value = "",
onValueChange = {},
label = { Text(stringResource(R.string.label_enter_value)) },
)
}
}
class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = ComposeView(inflater.context).apply {
setContent {
Column {
Text("Screen Two", color = Color.Blue, fontSize = 30.sp)
}
}
}
}
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 30
defaultConfig {
applicationId "com.example.composewithnavigation"
minSdk 28
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
viewBinding true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.10'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.ui:ui-viewbinding:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.0-beta01'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
}
As far as I know, fragments are discouraged in Compose for navigation. You are supposed to be creating multiple screens, like the FirstScreen Composable here. Anyway, I think the reason for the setContent not being called is that it is called from within the other setContent from your first fragment. Inside the setContent, you make the navigation call, which redirects it to the second fragment, which calls a setContent yet again. Because the second fragment will already be displayed inside the setContent of the FirstScreen Composable, your are actually nesting it, which might not be supported by compose , (I mean, makes sense).
That is why we recommend ditching the fragments and using Composables instead, which do not require explicitly calling setContent
Related
I added jetpack compose into my old project and I have noticed one problem with fragmentManager work.
In short, I have the following transactions:
MainActivity -> SettingsFragment -> BackgroundSettingsFragment.
When we go to BackgroundSettingsFragment and we press a back button, we return to SettingsFragment, but its content is empty. I debbuged SettingsFragment, after returning here, everything looks fine, but layout field is just empty.
I added breakpoints to different lifecycle events (onCreate, onCreateView, onViewCreated). Everything works great, I see that resources also is fine.
Without jetpack compose dependencies, there is no problem.
In details, I have the following code.
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion "$rootProject.kotlinVersion"
}
// Dependencies
// --- Jetpack Compose ---
implementation "androidx.compose.ui:ui:$compose_version"
// Integration with activities
implementation "androidx.activity:activity-compose:1.5.1"
// Integration with ViewModels
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
// Tooling support (Previews, etc.)
implementation "androidx.compose.ui:ui-tooling:$compose_version"
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation 'androidx.compose.foundation:foundation:1.2.0'
// Material Design
implementation "androidx.compose.material:material:$compose_version"
// Material design icons
implementation "androidx.compose.material:material-icons-core:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
// Integration with observables
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.runtime:runtime-rxjava2:$compose_version"
// Navigation
implementation "androidx.navigation:navigation-compose:2.5.1"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
MainActivity:
class MainActivity : RxAppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
redirectToSettingsFragment()
}
fun redirectToSettingsFragment() {
replaceFragmentSafely(SettingsFragment(), R.id.contentFrame)
}
}
SettingsFragment:
class SettingsFragment : RxFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupView()
}
fun setupView() {
label.setText(R.string.background_settings)
value.setText(R.string.color)
}
fun setupToolbar() {
(activity as MainActivity).toolbar.setOnBackButtonClickListener {
activity?.onBackPressed()
}
}
fun redirectToSecondFragment() {
replaceFragmentSafely(BackgroundSettingsFragment(), R.id.contentFrame, allowBackStack = true)
}
}
BackgroundSettingsFragment:
class BackgroundSettingsFragment : RxFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
}
fun setupToolbar() {
(activity as MainActivity).toolbar.setOnBackButtonClickListener {
activity?.onBackPressed()
}
}
}
fun AppCompatActivity.replaceFragmentSafely(fragment: Fragment,
#IdRes containerViewId: Int,
allowBackStack: Boolean = false) {
val fm = supportFragmentManager
.beginTransaction()
.replace(containerViewId, fragment, fragment.javaClass.toString())
if (allowBackStack) fm.addToBackStack(null)
try {
if (!supportFragmentManager.isStateSaved) {
fm.commit()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Here is the result.
What SettingsFragment looks like before we go to the BackgroundSettingsFragment:
What SettingsFragment looks like after we returned from the BackgroundSettingsFragment:
Please help, maybe someone already faced such a problem.
The Menu Host will not trigger with a MenuItem.itemId
I have attemped multiple diffent methods but all to lead nothing working.
The Menu that I have chosen to display shows up but the actions do not work
The object: MenuProvider
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object :MenuProvider{
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.maps_toggle,menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when(menuItem.itemId){
R.id.toggle_group->{
Log.d("FavPlaceOverserMaps1", "it.toString()")
if (isChecked){
favPlacesViewModel.readAllLocations.observe(viewLifecycleOwner, Observer {
Log.d("FavPlaceOverserMaps", it.toString())
})
}
true
}
else->{
false
}
}
}
},viewLifecycleOwner, Lifecycle.State.RESUMED)
}
I have a switch in the menu and the menu does not trigger when toggled
if(menuItem.isChecked == true){
menuItem.isCheck = isChecked
//do something
}
I am unsure if something is wrong as I have moved off the deprecated way of doing a menu handler
Activity Import
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
Dependency,
most dependency are here others include Firebase and Google
dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:2.5.1"
implementation "androidx.navigation:navigation-ui-ktx:2.5.1"
kapt "com.android.databinding:compiler:3.2.0-alpha10"
//implementation 'androidx.core:core-ktx:1.8.0'
implementation "androidx.activity:activity-ktx:1.5.1"
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}
According to this question, I tried to update my deprecated menus codes like setHasOptionsMenu , onCreateOptionsMenu and onOptionsItemSelected in my fragments and all app, but I should replace AppCompatActivity to ComponentActivity(R.layout.activity_example) but after doing this I see there's some problem, first I confused about how to use ViewBinding with it when I should remove setContentView(binding.root) from activity second the method setSupportActionBar(binding.appBarMain.toolbar) is not found, and I couldn't use the navigation components like supportFragmentManager and setupActionBarWithNavController
the third thing I couldn't"t declare this val menuHost: MenuHost = requireActivity() in onCreateView in fragment I see it's Required:
MenuHost
but Found:
FragmentActivity
here's my MainActivity code before edits
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private lateinit var postViewModel: PostViewModel
private lateinit var navGraph: NavGraph
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
postViewModel = ViewModelProvider(this)[PostViewModel::class.java]
postViewModel.getCurrentDestination()
setSupportActionBar(binding.appBarMain.toolbar)
val drawerLayout: DrawerLayout = binding.drawerLayout
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
if (navHostFragment != null) {
navController = navHostFragment.navController
}
navGraph = navController.navInflater.inflate(R.navigation.mobile_navigation)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.nav_favorites, R.id.about
), drawerLayout
)
// setupActionBarWithNavController(navController, appBarConfiguration)
// navView.setupWithNavController(navController)
setupActionBarWithNavController(this, navController, appBarConfiguration)
setupWithNavController(binding.navView, navController)
// determineAdvertisingInfo()
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
and this the implementation of menus in fragments
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
setHasOptionsMenu(true)
return binding.root
}
....................................................................................
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main, menu)
super.onCreateOptionsMenu(menu, inflater)
val searchManager =
requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
searchView.queryHint = resources.getString(R.string.searchForPosts)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(keyword: String): Boolean {
if (keyword.isEmpty()) {
Snackbar.make(
requireView(),
"please enter keyword to search",
Snackbar.LENGTH_SHORT
).show()
}
// itemArrayList.clear()
if (Utils.hasInternetConnection(requireContext())) {
postViewModel.getItemsBySearch(keyword)
postViewModel.searchedPostsResponse.observe(viewLifecycleOwner) { response ->
when (response) {
is NetworkResult.Success -> {
hideShimmerEffect()
itemArrayList.clear()
binding.progressBar.visibility = View.GONE
response.data?.let {
itemArrayList.addAll(it.items)
}
adapter.notifyDataSetChanged()
}
is NetworkResult.Error -> {
hideShimmerEffect()
// loadDataFromCache()
Toast.makeText(
requireContext(),
response.toString(),
Toast.LENGTH_LONG
).show()
}
is NetworkResult.Loading -> {
if (postViewModel.recyclerViewLayout.value == "titleLayout" ||
postViewModel.recyclerViewLayout.value == "gridLayout"
) {
hideShimmerEffect()
} else {
showShimmerEffect()
}
}
}
}
} else {
postViewModel.getItemsBySearchInDB(keyword)
postViewModel.postsBySearchInDB.observe(viewLifecycleOwner) { items ->
if (items.isNotEmpty()) {
hideShimmerEffect()
binding.progressBar.visibility = View.GONE
itemArrayList.clear()
itemArrayList.addAll(items)
adapter.notifyDataSetChanged()
}
}
}
return false
}
override fun onQueryTextChange(newText: String): Boolean {
return false
}
})
searchView.setOnCloseListener {
if (Utils.hasInternetConnection(requireContext())) {
Log.d(TAG, "setOnCloseListener: called")
itemArrayList.clear()
requestApiData()
} else {
noInternetConnectionLayout()
}
false
}
postViewModel.searchError.observe(viewLifecycleOwner) { searchError ->
if (searchError) {
Toast.makeText(
requireContext(),
"There's no posts with this keyword", Toast.LENGTH_LONG
).show()
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.change_layout) {
changeAndSaveLayout()
return true
}
return super.onOptionsItemSelected(item)
}
build.gradle dependencies
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation ('com.google.android.material:material:1.6.1') {
exclude(group: 'androidx.recyclerview', module: 'recyclerview')
exclude(group: 'androidx.recyclerview', module: 'recyclerview-selection')
}
implementation "androidx.recyclerview:recyclerview:1.2.1"
// For control over item selection of both touch and mouse driven selection
implementation "androidx.recyclerview:recyclerview-selection:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// //Moshi
// implementation("com.squareup.moshi:moshi:1.13.0")
// implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
// kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'org.jsoup:jsoup:1.14.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
implementation "androidx.browser:browser:1.4.0"
implementation 'androidx.multidex:multidex:2.0.1'
configurations {
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
}
//Room
implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
androidTestImplementation "androidx.room:room-testing:2.4.2"
//Dagger - Hilt
implementation 'com.google.dagger:hilt-android:2.42'
kapt 'com.google.dagger:hilt-android-compiler:2.42'
//SDP & SSP
implementation 'com.intuit.sdp:sdp-android:1.0.6'
implementation 'com.intuit.ssp:ssp-android:1.0.6'
// Shimmer
implementation 'com.facebook.shimmer:shimmer:0.5.0'
//firebase & analytics
implementation platform('com.google.firebase:firebase-bom:28.4.0')
implementation 'com.google.firebase:firebase-analytics'
//crashlytics
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-analytics'
// DataStore
implementation 'androidx.datastore:datastore-preferences:1.0.0'
implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
//admob
implementation 'com.google.android.gms:play-services-ads:21.1.0'
implementation platform('com.google.firebase:firebase-bom:30.2.0')
implementation project(':nativetemplates')
implementation("androidx.ads:ads-identifier:1.0.0-alpha04")
// Used for the calls to addCallback() in the snippets on this page.
implementation("com.google.guava:guava:28.0-android")
implementation 'com.google.firebase:firebase-analytics'
implementation("androidx.activity:activity-ktx:1.5.0")
}
Simplest solution from top of my head (hopefully it will work for you):
val menuHost: MenuHost = requireActivity() as MenuHost
This example is for fragment but I guess you can easily apply it to activity:
First extend fragment with MenuProvider:
class SomeFragment : SomethingIfYouHave(), MenuProvider { /* Your code */ }
Now in onCreateView:
val menuHost: MenuHost = requireActivity() as MenuHost
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
Later in code override onCreateMenu and onMenuItemSelected.
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.info_menu, menu)
}
override fun onMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.something-> {
// Do something here
true
}
else -> false
}
}
AppCompatActivity extends FragmentActivity which extends ComponentActivity, why to use ComponentActivity directly?
Please revert it to:
class MainActivity : AppCompatActivity() { /* Your code here */ }
I think your only problem was in this line:
val menuHost: MenuHost = requireActivity() as MenuHost
------------------- UPDATE
*ComponentActivity has all you need for a Compose-only app.
If you need AppCompat APIs, an AndroidView which works with AppCompat or MaterialComponents theme, or you need Fragments then use AppCompatActivity.
Reference*
You are not using purely ComposeUI, so that is why you need AppCompat and not ComponentActivity.
If you want to use ComponentActivity without AppCompat you need to get rid off supportFragmentManager.
This would go out of the scope but please check ComposeUI Fragments if you want to switch on ComposeUI (I didn't write article).
I'm getting this error while using Compose in my fragment which works fine in case of XML
ViewTreeLifecycleOwner not found from androidx.fragment.app.FragmentContainerView
I'm using a single activity approach without using Jetpack Navigation component
Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nav)
supportFragmentManager.commit {
setReorderingAllowed(true)
add<InboxFragment>(R.id.nav_fragmentContainerView_appNav)
}
}
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/nav_fragmentContainerView_appNav"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Fragment:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Text(text = "HELLO FRIEND!")
}
}
}
Dependencies:
def fragment_version = "1.3.3"
implementation("androidx.fragment:fragment-ktx:$fragment_version")
def compose_version = "1.0.0-beta06"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.activity:activity-compose:1.3.0-alpha07"
classpath "com.android.tools.build:gradle:7.0.0-alpha15"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
Since you are using an AppCompatActivity, only the appcompat 1.3 versions populate the ViewTreeLifecycleOwner.
Add:
implementation 'androidx.appcompat:appcompat:1.3.0'
None of these worked and there seems to be an open issue with BottomSheetDialog.
https://issuetracker.google.com/issues/261078350
I am trying to pass data from one fragment to another but I am facing issue with sending data from parcelable from one fragment to another.
class MainFragment : Fragment() {
companion object {
public val KEY_PARSE_DATA = "parseData"
}
private var parseData: ParseData? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_main, container, false).apply {
val editName = findViewById(R.id.edit_Name) as EditText
val editSurname = findViewById(R.id.edit_Surname) as EditText
val buttonNext = findViewById(R.id.btn_Next) as Button
buttonNext.setOnClickListener(View.OnClickListener {
val fragment = AnotherFragment()
if (parseData != null) {
var parseData = ParseData(editName.text.toString(), editSurname.text.toString())
val fragment = AnotherFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, parseData)
fragment.setArguments(bundle)
fragmentManager.beginTransaction().add(R.id.fragment_container, fragment).commitAllowingStateLoss()
}
})
}
}
}// Required empty public constructor
Parcelable class for implementing it
#SuppressLint("ParcelCreator")
#Parcelize
data class ParseData(val firstName: String, val lastName: String) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()) {
}
override fun toString(): String {
return "ParseData(firstName='$firstName', lastName='$lastName')"
}
companion object : Parceler<ParseData> {
override fun ParseData.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
}
override fun create(parcel: Parcel): ParseData {
return ParseData(parcel)
}
}
}
And another fragment which grab data from parcelable class in android
class AnotherFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_another, container, false).apply {
val textName = findViewById<TextView>(R.id.textFirst)
val textSurname = findViewById<TextView>(R.id.textSecond)
val bundle = arguments
if (bundle != null) {
val parseData = bundle.getParcelable<ParseData>(KEY_PARSE_DATA)
textName.setText(parseData.firstName)
textSurname.setText(parseData.lastName)
}
}
}
}
I tried some example but I cant get clear idea how parcelable is implemented in andoid application based on kotlin and build.gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlinsecond"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
androidExtensions {
experimental = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
Add below code into your App Level gradle.
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
androidExtensions {
experimental = true
}
Now it is enough to add 'kotlin-parcelize' plugin.
So for example:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
//
androidExtensions {
experimental = true
}
}
The order of plugin matters. kotlin-android must be before kotlin-android-extension. Then clean project.
Unable to add kotlin android extension
I'd recommend taking a look at the #Parcelize annotation provided with the Kotlin Android Extensions, it generates all necessary boilerplate for you so you don't have to write and maintain that code yourself.
Need to add these two in your app level Gradle
apply plugin: 'kotlin-android-extensions'
androidExtensions {
experimental = true`
}
Kotlin plugin should be enabled before 'kotlin-android-extensions'
so change the order to
apply plugin: 'com.android.application' #1
apply plugin: 'kotlin-android' #2
apply plugin: 'kotlin-android-extensions' #3
Hello All I had solved this question and there was issue with fragment transaction and parcelable
MainActivity to setup fragment
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragment = DetailFragment()
manager.findFragmentById(R.id.fragmentContainer)
manager.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
}
}
First fragment to fill data
class DetailFragment : Fragment(), View.OnClickListener {
var editTextName: EditText? = null
var editTextLast: EditText? = null
var buttonNextFragment: Button? = null
var firstName: String? = null
var lastName: String? = null
companion object {
val KEY_PARSE_DATA = "detailData"
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_detail, container, false)
editTextName = view.findViewById(R.id.ed_firstName) as EditText
editTextLast = view.findViewById(R.id.ed_lastName) as EditText
buttonNextFragment = view.findViewById(R.id.btn_nextfragment) as Button
buttonNextFragment!!.setOnClickListener(this)
return view
}
override fun onClick(v: View?) {
firstName = editTextName!!.text.toString()
lastName = editTextLast!!.text.toString()
Toast.makeText(context, firstName, Toast.LENGTH_SHORT).show()
// val viewFragment = ViewFragment()
// val transaction = fragmentManager.beginTransaction()
// transaction.replace(R.id.fragmentContainer, viewFragment)
// transaction.commit()
var details = Details(firstName!!, lastName!!)
val viewFragment = ViewFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, details)
viewFragment.setArguments(bundle)
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, viewFragment)
transaction.commit()
}
}
Display data coming from parcelable
class ViewFragment : Fragment() {
var textViewName: TextView? = null
var textViewLastName: TextView? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_view, container, false)
textViewName = view.findViewById(R.id.text_name_another) as TextView
textViewLastName = view.findViewById(R.id.text_surname_another) as TextView
val bundle = arguments
if (bundle != null) {
val details = bundle.getParcelable<Details>(KEY_PARSE_DATA)
textViewName!!.setText(details.firstName)
textViewLastName!!.setText(details.lastName)
}
return view
}
}
Model class for implementation of parcelable
#Parcelize
class Details(val firstName: String, val lastName: String) : Parcelable
and my gradle file
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlintwo"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
androidExtensions {
experimental = true
}
}