ConstraintLayout's Flow vs. Fragments programaticaly - android-constraintlayout

I have Flow in my ConstraintLayout set up like this:
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/subjects"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:flow_maxElementsWrap="2"
app:flow_wrapMode="aligned"
app:constraint_referenced_ids="test1,test2,test3"
app:layout_constraintTop_toBottomOf="#id/pair_your_device_subtitle" />
<!-- The test ids are just simple Views with colorful background -->
And it works, the test views are arranged in 2 columns.
Now, I have list, wchich is transformed to Fragments and I want those Fragments to be arranged in this Flow.
So I have the code like following:
childFragmentManager.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
if (f is SnapshotFragment) {
Timber.d("onFragmentViewCreated ${v.id}")
require(v.id != View.NO_ID) // id is generated in SnapshotFragment with View.generateId()
subjects.addView(v)
// Testing
subjects.addView(View(requireContext()).apply {
id = View.generateViewId()
layoutParams = ViewGroup.LayoutParams(400, 400)
background = ColorDrawable(resources.getColor(android.R.color.black))
constraint.addView(this)
})
Timber.d("onFragmentViewCreated ${subjects.referencedIds.asList()}")
}
}
},
false
)
childFragmentManager.commit {
subjectsList.forEach { subject ->
add(
constraint.id,
SnapshotFragment::class.java,
bundleOf(SnapshotFragment.SUBJECT_KEY to subject)
)
}
}
And this doesn't work.
Well, the callbacks are called, the ids in logs looks good, the black testing views are added correctly, but the fragment's views are stuck at the top left corner.

https://issuetracker.google.com/issues/159516508
This issue was the cause. When I changed view id generation from onViewCreated() directly to onCreateView(), it started working.

Related

Jetpack compose LazyColumn or Scrollable Column and IME padding for TextField doesn't work

I am trying to set a list if text fields, and when user set the focus on one text field at the bottom I expect that the user can see the appearing IME soft keyboard and the text field being padded according to the configuration set in my manifest file android:windowSoftInputMode="adjustPan", but it doesn't work the first time, it works only when some of the listed textfield have already a focus.
Behavior in video.
My code in onCreate method.
// Turn off the decor fitting system windows, which allows us to handle insets,
// including IME animations
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
// Provide WindowInsets to our content. We don't want to consume them, so that
// they keep being pass down the view hierarchy (since we're using fragments).
ProvideWindowInsets(consumeWindowInsets = false) {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background, modifier = Modifier.systemBarsPadding()) {
Column(modifier = Modifier.fillMaxHeight()) {
val list: List<#Composable () -> Unit> = (1..10).map {
{
Text(text = "$it")
Divider()
TextField(value = "", onValueChange = {}, modifier = Modifier.navigationBarsWithImePadding(),)
}
}
LazyColumn(modifier = Modifier.fillMaxSize().weight(1F)) {
itemsIndexed(list) { index, inputText ->
inputText()
}
}
}
}
}
}
}
If your problem is going on, check Insets for Jetpack Compose.
Only add this dependency:
implementation 'com.google.accompanist:accompanist-insets:{insetsVersion}'
and use Modifier.imePadding() or the custom solution:
#Composable
fun ImeAvoidingBox() {
val insets = LocalWindowInsets.current
val imeBottom = with(LocalDensity.current) { insets.ime.bottom.toDp() }
Box(Modifier.padding(bottom = imeBottom))
}

How do I render different shapes on each row of a SAPUI5 Gantt chart?

In my application, I have to render Projects, Tasks and Milestones. Projects and Tasks are differently coloured bars, and the Milestone is a Diamond (I'm using BaseRectangle and BaseDiamond respectively).
Since some items in my hierarchy are Projects, Some Tasks and Some Milestones, how can I render differing shapes on each row?
My first thought was to use the common "visible" property, but shapes don't have that, conversely "opacity" makes things invisible, but they still respond to mouse position.
I then tried using an Aggregation factory function, but although my chart renders correctly on first display, it doesn't recalculate the shapes on expanding or collapsing branches.
It seems to me that the factory function should work, but something is breaking in the chart that doesn't throw errors to console.
At the moment in my XML template, I have the following:
rowSettingTemplate has shapes1={path: factory:} and no shapes1 element.
Each of my BaseShapes is in a different fragment which are attached to my TreeTable as dependents.
Example Shape Fragment - Project.fragment.xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:gnt2="sap.gantt.simple">
<gnt2:BaseRectangle id="shapeProject"
shapeId="{plandata>id}" countInBirdEye="true"
time="{plandata>start_date}" endTime="{plandata>end_date}"
resizable="true" selectable="true" draggable= "true" connectable="true"
title="{plandata>text}" showTitle="true"
tooltip=""
fill="#0c1" />
</core:FragmentDefinition>
Factory function:
shapeFactory: function(sId, oContext) {
var parentId = (/(.*)-\d+$/.exec(sId))[1];
var rowSettings = sap.ui.getCore().byId(parentId);
var node: Project.Node = oContext.getProperty();
if (String(node.id) == rowSettings.getProperty("rowId")) {
switch (node.type) {
case "project":
return this.byId('shapeProject').clone(sId);
case "task":
return this.byId('shapeTask').clone(sId);
case "milestone":
return this.byId('shapeMilestone').clone(sId);
default:
return this.byId('shapeErr').clone(sId);
}
} else {
return this.byId('shapeEmpty').clone(sId);
}
}
My empty shape is a BaseGroup - note that SAPUI5 crashes if I return a null from factory, so something has to be returned when I actually want nothing.
I also tried wrapping all my shapes in BaseGroup so that the chart always sees the same control type, but that doesn't work. Note also that if I return a clone of Empty each time without any special logic, then the chart works correctly.
I'm hoping that this is a settings or something to ensure that the aggregation works properly each time. My SAPUI5 version is 1.61.2 — I'll try 1.63.1 when I get some time, but I think that this issue is fairly deep down.
If anybody has any ideas or sample code, it would be greatly appreciated.
I have come up with a workaround for this, that may save somebody several hours. Basically instead of defining the shapes1 aggregation via a factory function, I have used the <shapes1> tag instead. My Shapes1 tag contains a reference to my own custom shape which derives from BaseRectangle. My custom shape can then render whatever SVG it requires based on the bound object context. Now my tree can expand and collapse whilst rendering whatever shapes are required.
My renderer now looks like this:
CustomChartShape.prototype.renderElementRectangle = BaseRectangle.prototype.renderElement;
CustomChartShape.prototype.renderElementDiamond = BaseDiamond.prototype.renderElement;
CustomChartShape.prototype.renderElement = function(oRm, oElement) {
// There is possibilities that x is invalid number.
// for instance wrong timestamp binded to time property
if (this.bHasInvalidPropValue) { return; }
var Node = this.getBindingInfo('endTime').binding.getContext().getProperty();
if (Node.type == "milestone") {
this.renderElementDiamond(oRm, oElement);
} else {
this.renderElementRectangle(oRm, oElement);
}
}
I had to provide a 'getD' function that has a fixed width, and I'll have o go through and rewrite several functions, but I think that this will work for me.

Fantom fwt Combo widget seems to fire modify event on construction

I have written this Fantom class
using gfx
using fwt
class Test {
Window window := Window {
size = Size( 400, 320 )
SashPane {
Combo {
items = Month.vals
onModify.add( |e| { echo( "items.size is ${e->widget->items->size}" ) } )
},
},
}
Void main() {
window.open
}
}
when I run it, it produces this output:
items.size is 12
items.size is 12
which means that the modify event is being triggered twice. It occurs at the same time the window pops up on the screen, without me having any chance to modify anything on the Combo widget. Why?
This is causing issues in a real class that uses multiple Combo widgets, some of them related and causing a cascade of events that produces unexpected results.
Is there any way this can be prevented, please?
I can confirm that this is an issue.
Looking at the Java source code for the FWT Combo, it's pretty small and doesn't seem to do anything wrong, which leads me to believe that it's a issue with the SWT Combo Widget.
That doesn't help you any, so I had a quick play with the example and found this work around...
...add the onModify event listener after the window has been opened, and the widgets constructed. Do this by using the Window.onOpen() event:
using gfx
using fwt
class Testy {
Void main() {
Window {
size = Size( 400, 320 )
combo := null as Combo
onOpen.add {
combo.onModify.add { echo("Hello Mum!") }
}
SashPane {
combo = Combo { items = Month.vals },
},
}.open
}
}
Now you should only get a Hello Mum! when the combo is actually modified.

Hiding UI element from fragment.xml in standard App

I want to hide few UI elements from My Travel and Expense (Standard App). I have tried in different approaches but I am not able to achieve what i want. Here is my requirement:
In My Travel and Expense App (TRV_TE_CRE), I want to hide the following UI elements:
GenericClaim.fragment.xml - Button id="costAssignmentButton"
I have added the extension project for TRV_TE_CRE and tried as below:
In component.js I added the following statement to hide
customizing:
{
"sap.ui.viewModifications": {
"mytravelandexpense.view.GenericClaim": {
"costAssignmentButton": {
"visible": false
},
},
},
Result: not working
Extended the GenericClaim.controller.js:
I added the below code in hookmethod
this.byFragmentId("costAssignmentButton").setVisible(false);
Result : whole claim page is not loading
By using access key I have commented the UI code in GenericClaim.fragment.xml
Result : not getting hide
Instead of the fragment ID, you can access the element ID from the view. Add this method in your view controller.
onAfterRendering : function(){
var buttonToHide = this.getView().byId("costAssignmentButton");
buttonToHide.setVisible(false);
},

sap.m.TileContainer scrollIntoView issue

I have an XML view that contains a TileContainer which is bound to a model that is used to create StandardTiles. The XML snippet is:
<TileContainer id="tilelist" tiles="{Applications}">
<tiles>
<StandardTile name="{ID}" icon="{Icon}" title="{Name}" press="doNavigation" info="{Description}"
number="{path : 'Number', formatter: 'linxas.com.fiori.launchpad.util.Formatter.formatUsingURL'}"
numberUnit="{NumberUnit}"/>
</tiles>
</TileContainer>
This is working perfectly, the correct tiles are getting displayed etc. When I click on a tile, there is navigation that occurs and I want to "remember" which tile was clicked (by index) so when returning I can scroll to that tile. This is done on the tile's press event handler (doNavigation function) and stores the index in sessionStorage. This is also working properly.
doNavigation : function (evt) {
if (sessionStorage && this.getView().byId('tilelist')) {
sessionStorage.setItem("selected_tile", this.getView().byId('tilelist').indexOfTile(evt.getSource()));
}
...
}
The proper value is stored. So when navigating back, within the onAfterRendering function of the page that contains the TileContainer I have the following code. It is attempting to see if there is a "selected_tile" value stored in sessionStorage, if so it calls scollIntoView passing in the tile index. The issue is that this code is executed, but doesn't work and I suspect it is because at the time of calling this function, the TileContainer's tiles aggregation is returning 0 length.
onAfterRendering : function (evt) {
var theList = this.getView().byId("tilelist");
if (sessionStorage && theList) {
var tile_index = sessionStorage.getItem("selected_tile");
console.log(tile_index + " of " + theList.getTiles().length);
if (tile_index) {
theList.scrollIntoView(+tile_index, true);
sessionStorage.removeItem("selected_tile");
}
}
}
My console output looks something like this (based on the tile that was clicked):
5 of 0
Any help would be appreciated. I assume that there is somewhere else that I need to execute this last bit of code as the TileContainer does not seem to be finished processing its tiles at this point, at least that is my assumption of why the tiles aggregation is 0.
Are you using Routing in your project?
If yes, you can try to register a method to handle the routePatternMatched event of the router. This method will be called after the onAfterRendering method - if the proper route pattern is matched.
To achieve this, just create the following:
onInit: function() {
sap.ui.core.UIComponent.getRouterFor(this).getRoute("NameOfYourCurrentRoute").attachPatternMatched(this._routePatternMatched, this);
},
_routePatternMatched: function(oEvent) {
//do your stuff here
},
Hopefully the TileList is ready at this point to navigate to the correct tile.