Skip to content

feat(android): change from Fragment to a normal View - #703

Open
m1ga wants to merge 16 commits into
masterfrom
androidView
Open

feat(android): change from Fragment to a normal View#703
m1ga wants to merge 16 commits into
masterfrom
androidView

Conversation

@m1ga

@m1ga m1ga commented May 13, 2025

Copy link
Copy Markdown
Contributor

I think it is currently the only module that uses Fragments instead of a normal view. This had some issues before (ListView header) and it will fix tidev/titanium-sdk#14214

Also matches the same library versions as #678 (merge first for the action fixes).

Tested:

Changes:

  • move from Fragment to normal View
  • update libraries
  • added some null pointer checks
  • Replaced manual InputStream close with try-with-resources — no leak if IOException occurs
  • Added null guard for getAppCurrentActivity() + added missing Activity import — prevents NPE when app is in background
  • Wrapped MotionEvent copy usage in try/finally with evCopy.recycle() — prevents pooled event leak
  • Added removeView(mMapView) from parent before onDestroy() — prevents view leak from dangling in container
  • Added preloadOverlaysList.clear() to clearPreloadObjects() — fixes memory leak of image overlays added before map is ready
  • optimized saved-state code

ti.map-android-6.0.0.zip
(updated 26/07/30 - 15:40)

Lite mode check:

var win = Titanium.UI.createWindow({
	layout:"vertical"
});
var Map = require('ti.map');

function createMap(liteMode) {
	var map = Map.createView({
		top: 10,
		right: 0,
		width: Ti.UI.FILL,
		height: 200,
		liteMode: liteMode,
		region: {
			zoom: 10,
			latitude: 46.893234,
			longitude: 1.346569,
		}
	});
	var an = Map.createAnnotation({
		title: 'Title',
		latitude: 46.893234,
		longitude: 1.346569,
	})
	map.addAnnotation(an);
	win.add(map);
}

createMap(true);	// liteMode map
createMap(false);	// normal map
win.open();

ListView Header/Footer example

var Map = require('ti.map');

var map = Map.createView({
	top: 10,
	right: 0,
	width: Ti.UI.FILL,
	height: 200,
	region: {
		zoom: 10,
		latitude: 46.893234,
		longitude: 1.346569,
	}
});
var map2 = Map.createView({
	top: 10,
	right: 0,
	width: Ti.UI.FILL,
	height: 200,
	region: {
		zoom: 10,
		latitude: 46.893234,
		longitude: 1.346569,
	}
});
var an = Map.createAnnotation({
	title: 'Title',
	latitude: 46.893234,
	longitude: 1.346569,
})
map.addAnnotation(an);

var win = Ti.UI.createWindow();
var listView = Ti.UI.createListView();
var sections = [];

var fruitDataSet = [];
for (var i = 0; i < 100; i++) {
	fruitDataSet.push({properties: {
		title: "t" + i
	}});
}
var fruitSection = Ti.UI.createListSection({
	headerView: map,
	items: fruitDataSet
});
sections.push(fruitSection);

listView.sections = sections;
win.add(listView);
win.open();

var fishDataSet = [{
		properties: {
			title: 'Cod'
		}
	}
];
var fishSection = Ti.UI.createListSection({
	footerView: map2,
	items: fishDataSet
});
listView.appendSection(fishSection);

Saved state test:

var Map = require('ti.map');

var win = Ti.UI.createWindow({ title: 'Restore test' });

// Intentionally NO `region` property: a fixed region would be re-applied on
// every view rebuild and mask whether the camera actually came back from the
// saved instance state.
var mapview = Map.createView({
      top: 40,
      mapType: Map.NORMAL_TYPE,
      annotations: [Map.createAnnotation({
              latitude: 48.137,
              longitude: 11.575,
              title: 'Munich'
      })]
});

var label = Ti.UI.createLabel({
      top: 0,
      height: 40,
      text: 'pan/zoom somewhere, then leave & return'
});

mapview.addEventListener('regionchanged', function (e) {
      label.text = 'lat ' + e.latitude.toFixed(4)
              + '  lon ' + e.longitude.toFixed(4);
});

win.add(label);
win.add(mapview);
win.open();
  1. On the device/emulator enable Developer options → Don't keep activities (this forces the activity to be destroyed with onSaveInstanceState on every Home press and recreated with the bundle on return — the exact path ViewProxy.onCreate → getSavedInstanceState() → MapView.onCreate(savedState) now covers).
  2. Launch the app, pan/zoom to a distinctive spot (note the lat/lon in the label).
  3. Press Home, then reopen the app from recents.

Expected with the fix: the map comes back at the position you left it, and it's still alive — panning fires regionchanged (label updates) and the Munich marker is tappable.

@hansemannn hansemannn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Important: the MapView refactor only forwards onResume() and onDestroy(), but not onStart(), onPause(), onStop(), low-memory, or saved-state callbacks. See android/src/ti/map/TiUIMapView.java:96, android/src/ti/map/TiUIMapView.java:1443, and android/src/ti/map/ViewProxy.java:125. titanium-sdk shows these lifecycle hooks are available on proxies, so this is a real integration gap, not a platform limitation. The result is likely leaked map resources and broken restore/background behavior after activity pause/recreate. Fix by forwarding the full activity lifecycle to MapView, and if recreation is expected to preserve state, wire instance-state callbacks through to MapView.onCreate(savedState) / onSaveInstanceState().

  2. Important: liteMode and zOrderOnTop were creation-time options before this change, but the new code always constructs a plain new MapView(...) and only reads liteMode later as a boolean flag. See android/src/ti/map/TiUIMapView.java:96 and android/src/ti/map/TiUIMapView.java:250. That means both public properties are now effectively ignored, which is a behavior regression. Fix by building GoogleMapOptions from the proxy properties before creating the MapView.

@hansemannn

Copy link
Copy Markdown
Contributor

Thanks for the updates! Two issues are still open:

  1. MapView still never gets a real onResume() after backgrounding. ViewProxy forwards onStart(), onPause(), and onStop(), but there is still no onResume(Activity) override, so the map can be paused/stopped and never resumed.
  2. Saved-state handling is still wrong. TiUIMapView calls mMapView.onCreate(null) in the constructor and may call mMapView.onCreate(savedState) again later from onResume(). That gives one MapView instance two onCreate() calls, and there is still no proper onSaveInstanceState() bridge even though Titanium core supports it.

The earlier liteMode/zOrderOnTop regression and the iOS workflow SDK mismatch look fixed.

@m1ga
m1ga marked this pull request as draft April 16, 2026 09:33
@m1ga

m1ga commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback, I'll do some more tests here 👍

@hansemannn
hansemannn marked this pull request as ready for review April 16, 2026 10:59

@hansemannn hansemannn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code-wise, this looks very well now! Feel free to merge once functionally tested.

@m1ga

m1ga commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Hmmm... @Override public void onLowMemory(Activity activity) shouldn't work. onLowMemory is not in KrollProxy

@hansemannn

Copy link
Copy Markdown
Contributor

You are right, it was inferred from the others. Would be good to expose it on the SDK side, but no blocker here anymore.

@m1ga

m1ga commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

It looks like it is fixed with 13.3.0 (by accident 🤣👍 ). So we don't need to switch the map from fragment to view. Tested it with 13.2.0 and there it is still crashing but with 13.3.0 it's working fine

@m1ga m1ga closed this Jul 19, 2026
@m1ga
m1ga deleted the androidView branch July 19, 2026 18:36
@m1ga
m1ga restored the androidView branch July 30, 2026 10:30
@m1ga

m1ga commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Have to open it again. It looks like there still some fragment crashes with the current map in different scenarios. In one client app restarting the app with the tab group as the selected tab crashed with the AndroidRuntime: java.lang.IllegalArgumentException: No view found for id 0x2 (unknown) for fragment SupportMapFragment{80554da} error. Using this PR fixed it

@m1ga m1ga reopened this Jul 30, 2026
@m1ga

m1ga commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

If you are still fine with the changes @hansemannn I would merge this one so we can have this version for 14.0.0.

After this is merged I'll update the bugfix PR #717 so it apply all the fixes to the view version and release a new version that people can use if they like (or need). And with Ti SDK 14.0.0 we'll ship this version right away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: experimental BottomNavigation doesn't work with map

2 participants