I'm currently exploring the new MvRx stack from Airbnb
Rigth now i am adding a google maps fragment successfully to a BaseMvRxFragment in a BottomNavigationView.
The problem is that the 2. time i navigate to the MapFragment my app crashes and i get the following error message:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: getflareapp.com.s2s, PID: 19184
android.view.InflateException: Binary XML file line #7: Binary XML file line #7: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class fragment
Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f080093, tag null, or parent id 0xffffffff with another fragment for com.google.android.gms.maps.SupportMapFragment
at android.support.v4.app.FragmentManagerImpl.onCreateView(FragmentManager.java:3752)
at android.support.v4.app.FragmentController.onCreateView(FragmentController.java:120)
at android.support.v4.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:405)
at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:387)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:780)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
at android.view.View.inflate(View.java:23239)
at getflareapp.com.s2s.views.MapView.<init>(MapView.kt:19)
at getflareapp.com.s2s.views.MapView.<init>(MapView.kt:14)
at getflareapp.com.s2s.views.MapView.<init>(Unknown Source:6)
at getflareapp.com.s2s.views.MapViewModel_.buildView(MapViewModel_.java:42)
at getflareapp.com.s2s.views.MapViewModel_.buildView(MapViewModel_.java:22)
MapFragment.kt
/**
* A simple [BaseFragment] subclass.
*
*/
class MapFragment : BaseFragment() {
private val mViewModel: ChatViewModel by fragmentViewModel()
override fun epoxyController() = simpleController(mViewModel) {state ->
mapView{
id("map")
}
}
}
MapView.kt
@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT)
class MapView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
init {
// TODO: Fix crash on second view.
inflate(context, R.layout.view_map, this)
}
}
view_map.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map2"
android:name="getflareapp.com.s2s.ui.map.MapFragment"
class="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity" />
</FrameLayout>
Thanks for any help! <3
I've never used fragments inside an epoxy model, so I'm not sure how to make this work off the top of my head, or even if it is feasible. My first guess is that you need to remove the fragment from the activity when the model is recycled.
If your only view is a map I wouldn't use epoxy for this - just use a normal layout with mvrx
Thanks, that is what i ended up doing.
If anybody is wondering, using a MapView in lite mode works just fine in an Epoxy view.
Just put a MapView in a layout xml file:
<com.google.android.gms.maps.MapView
android:id="@+id/my_location_map"
android:layout_width="match_parent"
android:layout_height="220dp"
app:cameraZoom="15"
app:liteMode="true"/>
Then the view (in Kotlin), MyMapView.kt:
@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class MyMapView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private var marker: Marker? = null
private var myLocation: LatLng? = null
private val markerOptions = MarkerOptions()
private var googleMap: GoogleMap? = null
init {
View.inflate(context, R.layout.my_map_view_layout, this)
my_location_map.isClickable = false
my_location_map.isLongClickable = false
}
fun startMap() {
my_location_map.onCreate(null)
my_location_map.getMapAsync {
googleMap = it
googleMap?.uiSettings?.isMapToolbarEnabled = false
myLocation?.let {
googleMap?.moveCamera(CameraUpdateFactory.newLatLng(it))
markerOptions.position(it)
marker?.remove()
marker = googleMap?.addMarker(markerOptions)
}
}
}
@ModelProp
fun setLocation(newLocation: LatLng?) =
newLocation?.let {
myLocation = it
startMap()
}
}
Then in the Epoxy controller:
private fun renderLoadedState(data: MyState) {
/**
* [MyMapView]
* data.myLocation is a LatLng
*/
myMapView {
id("my_map_view")
location(data.myLocation)
}
//.................
Note that if you use a MapView in regular mode (not litemode) you'll need to route all of the Activity lifecycle events down to the MapView as well.
Can confirm solution by @dmnugent80 is working for me. Thanks!
@dmnugent80, how does one route the MapView in a regular mode considering a property of it is required in the activity/fragment class and epoxy is not structured to allow for that?
Most helpful comment
If anybody is wondering, using a MapView in lite mode works just fine in an Epoxy view.
Just put a MapView in a layout xml file:
Then the view (in Kotlin), MyMapView.kt:
Then in the Epoxy controller:
Note that if you use a MapView in regular mode (not litemode) you'll need to route all of the Activity lifecycle events down to the MapView as well.