Android NavController get backstack - android-architecture-navigation

We use NavController.OnDestinationChangedListener and the provided parameter NavDestination to hide/show the bottom navigation bar (which is anchored in the root activity) depending on the current destination. But our logic requires also to know which was the previous destination for the current destination in order to hide/show the bottom navigation bar.
NavController.OnDestinationChangedListener also provides a NavController parameter, and NavController has an internal property called mBackStack which seems to be precisely what we need. So, is there a way to access the NavController backstack without using reflection?
Thanks!

There is a way to infer the NavDestination(s) which are currently in the backstack by accessing all the destinations in the graph and then calling navController.getBackStackEntry in each one of them.
val destinationsInBackStack = navController.graph.mapNotNull { dest ->
try {
navController.getBackStackEntry(dest.id)
dest
} catch (e: IllegalArgumentException) {
null
}
}

Related

SwiftUI: Why does ForEach need an ID?

Im using a ForEach loop in my SwiftUI View and I am getting strange warnings.
It works fine like this:
ForEach(0..<7) { i in
// do something
}
Then I changed 7 to a constant:
let numberOfElements = 7
ForEach(0..<numberOfElements) { i in
// do something
}
And got the following warning:
Non-constant range: argument must be an integer literal
I googled an found the following solution which works:
let numberOfElements = 7
ForEach(0..<numberOfElements, id:\.self) { i in
// do something
}
However, I have no idea why it works. Why do I have to give an ID to the ForEach loop, and what is the ID for?
ForEach(0..<numberOfElements) { i in
// do something
}
The reason why using the above ForEach init pops the using literal value warning is because SwiftUI doesn't expect to re-render anything when using the Range<Int> init method. This is a documented requirement / feature. The exceptions are the init methods with id:.
A hashable id matters in SwiftUI as well as in many other view-tree based frameworks like React is because these UI frameworks needs to use these ids to track updates for views inside the ForEach or any other "Container Views", to help the framework achieve usable performance. If you want to dig deeper, take a look at this WWDC video: Demystify SwiftUI.

How to move a room on the list (WWDC 2019 video 204)?

So I'm following along making the SwiftUI list of rooms and I'm stuck on how to fix the move function.
I've looked through the list of methods but I can't seem to determine which one it would be as it seems that the move method no longer exists.
func move(from source: IndexSet, to destination: Int)
{
store.rooms.move(fromOffSets: source, toOffset: destination)
}
Value of type '[Room]' has no member 'move'; did you mean 'remove'?
I don't mean to remove but simply to be able to move the room on the list.
I fought with this as well. My best guess is that for the presentation they are using an api surface area that isn't part of the preview yet. I've found several things that were presented that are not available. Form View is another one for instance.

Avoiding downcasts in a Swift 3 completion handler with Google Drive REST API

I'm using the Google Drive REST API in a Swift 3 app. Queries are executed using the executeQuery method of GTLRDriveService. This method takes a completion block of type GTLRServiceCompletionHandler?, which in turn is declared as
public typealias GTLRServiceCompletionHandler = (GTLRServiceTicket, Any?, Error?) -> Swift.Void
Because of this declaration, the second parameter must be downcast to the appropriate type inside the block. For instance:
let createPermissionQuery = GTLRDriveQuery_PermissionsCreate.query(
withObject: permission, fileId: toShare.id)
...
driveService.executeQuery(createPermissionQuery) { (ticket, result, error) in
if (error == nil) {
// need to downcast result to GTLRDrive_Permission
let permission = result as! GTLRDrive_Permission
...
}
}
The second parameter is always of a specific type that is completely determined by the particular query passed to executeQuery. For instance, if one passes an instance of GTLRDriveQuery_PermissionsCreate, then the second parameter (if the query succeeds) will always be of type GTLRDrive_Permission. However, if I try to declare result to be of any type other than Any?, the code won't compile.
In Objective C, the completion block can be specified with a type that's specific to the query. For instance (adapted from here):
GTLRDriveQuery_PermissionsCreate *createPermissionQuery =
[GTLRDriveQuery_PermissionsCreate queryWithObject:permission
fileId:fileId];
...
[driveService executeQuery:createPermissionQuery
completionHandler:^((GTLRServiceTicket *ticket,
GTLRDrive_Permission *permission,
NSError *error) {
if (error == nil) {
// work directly with permission
...
}
}];
Is there any way to avoid this downcast? (I'm asking out of ignorance; I'm somewhat of a newbie to Swift.) If I was writing my own library, I'd design the method signatures differently, but this is Google's library and am kind of stuck with what they supply. Perhaps some sort of extension or layer on top of Google's code?
You might be able to specify an extension that wraps the Google execute method, takes a generic and casts to your generic type in the block. This would basically just be a pretty abstraction of what you're doing already, but for all types your extension would be designed to cover.

Xcode simulator not calling traitCollectionDidChange

I am overriding traitCollectionDidChange(_) to update my compact and regular constraints. When I test this on a device by rotating the constraints get updated correctly. When I however try to test the same code in the simulator nothing happens. I interjected print statements and I can see that on simulator rotation nothing happens. Is this a bug, or do I need to do something special for the simulator?
Thanks in advance. I am using Xcode 8.2.1 btw.
This is my code:
private var compactConstraints: [NSLayoutConstraint] = []
private var regularConstraints: [NSLayoutConstraint] = []
private var sharedConstraints: [NSLayoutConstraint] = []
...
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if (!sharedConstraints[0].isActive) {
// activating shared constraints
NSLayoutConstraint.activate(sharedConstraints)
}
if (self.traitCollection.containsTraits(in: UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClass.compact))) {
print("going to activate: compact")
if regularConstraints.count > 0 && regularConstraints[0].isActive {
NSLayoutConstraint.deactivate(regularConstraints)
}
// activating compact constraints
NSLayoutConstraint.activate(compactConstraints)
} else {
print("going to activate: regular")
if compactConstraints.count > 0 && compactConstraints[0].isActive {
NSLayoutConstraint.deactivate(compactConstraints)
}
// activating regular constraints
NSLayoutConstraint.activate(regularConstraints)
}
}
The console output is as follows:
[launch app]
`going to activate: compact`
[rotate the simulator with ⌘ arrow key]
`going to activate: compact`
Small update based on hoshy's question:
I am using the simulator with iPhone devices. Specifically iPhone SE.
horizontalSizeClass is Compact for both orientations. verticalSizeClass is regular for portrait and Compact for landscape for iPhone SE. You can change this line
if (traitCollection.containsTraits(in: UITraitCollection(verticalSizeClass: .compact))) {
or simply
if traitCollection.verticalSizeClass == .compact {
You can also use viewWillTransitionToSize:withTransitionCoordinator: if you are looking for animation to run alongside the size change animation.
Building an Adaptive Interface from apple developer
If your Auto Layout constraints are insufficient to achieve the look
you want, you can use the
viewWillTransitionToSize:withTransitionCoordinator: method to make
changes to your layout. You can also use that method to create
additional animations to run alongside the size-change animations. For
example, during an interface rotation, you might use the transition
coordinator’s targetTransform property to to create a counter-rotation
matrix for parts of your interface.
Could it be that your actual test device is an iPhone and your simulator is an iPad? The traitCollectionDidChange method will not be called on the latter, since both orientations would be 'regular'.

Google Maps Android API v2 getVisibleRegion() returns 0

I am using the new Google Maps Android API v2 and need to detect latlng coordinates for the corners of my screen view. I use mMap.getProjection().getVisibleRegion() to do this, which works fine the first time I load the program, but if I leave the program and then re-open it (either via the back button or the home button) getVisibleRegion() returns latlng coordinates that are all (0.0, 0.0). I have a workaround where I save the VisibleRegion object as a global in the application class when I first open the program, but this seems like a bad idea. Anyone understand why the latlng coordinates go to zero (but not null) when re-opening? Thanks!
Edit: sometimes initial load gives a (0.0, 0.0) Visible Region so my workaround is not viable. Using getProjection().fromScreenLocation(point) also returns 0.
Okay, I think I solved it: I was guessing right: The map was not visible and therefore there is also no visibleRegion (in other words a region from 0,0,0,0 to 0,0,0,0). Similar to this question
Android Google Maps API v2 calling getProjection from onResume
I attached an onCameraChanged-Listener to my map. Everytime the map is moved (or the camera is available => There is a visibleRegion) this method is called.
private void initMap() {
mMap = getMap();
if (mMap != null) {
try {
mMap.setLocationSource(this);
mMap.setOnMarkerClickListener(this);
mMap.setOnCameraChangeListener(new OnCameraChangedListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
Log.d(TAG, "onCameraChange");
updateMapItems();
}
}
MapsInitializer.initialize(getActivity());
...
}
}
}
This listener makes sure that you have a visibleRegion.
UPDATE: Since the new update of the Google Maps API v2, there is a callback onMapReady(). I did not use it yet, but it seems to serve exactly the purpose of this question: https://developer.android.com/reference/com/google/android/gms/maps/OnMapReadyCallback.html