Expo SDK36 + Detox - Hanging on reloadApp() - ios

Detox is not working with Expo SDK 36?
Debugging reveals that it's hanging on await device.launchApp() in reloadApp() within detox-expo-helpers.
I've gone through the setup highlighted in:
Is it actually possible to make Detox/Jest tests pass with a React Native app running with Expo?
Example of my test:
const { reloadApp } = require('detox-expo-helpers');
describe('Example', () => {
beforeEach(async () => {
await reloadApp();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});
package.json
"scripts": {
"e2e": "detox test --configuration ios.sim",
},
"devDependencies": {
"babel-eslint": "^10.0.3",
"babel-preset-expo": "^7.0.0",
"detox": "^15.2.2",
"detox-expo-helpers": "^0.6.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-prettier": "^3.1.1",
"expo-detox-hook": "^1.0.10",
"jest-expo": "^35.0.0",
"pusher-js-mock": "^0.2.0",
"react-native-testing-library": "^1.12.0",
"react-test-renderer": "^16.12.0"
},
"private": true,
"detox": {
"configurations": {
"ios.sim": {
"binaryPath": "bin/Exponent.app",
"type": "ios.simulator",
"name": "iPhone 11"
}
},
"test-runner": "jest"
}
Even if I bypass the hanging promise via
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
beforeEach(async () => {
reloadApp();
await timeout(12000);
});
It hangs on await expect(element(by.id('welcome'))).toBeVisible();
What am I doing wrong?

Related

React Native - fontFamily "Inter-Black" is not a system font and has not been loaded through Font.loadAsync

I've already seen every stack question about this issue but nothing works. Already changed versions, fonts, type of fonts and the "old" Font.loadAsync. My code is basically equal of the offcial documentation: Font Expo Documentation
The error:
My device is IOS. Here's my code:
import { useCallback} from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import Menu from './screens/menu';
import FEMIntro from './screens/femIntro';
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();
export default function App() {
let [fontsLoaded] = useFonts({
'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
//'WorkSansRegular': require('./assets/fonts/WorkSans-Regular.ttf'),
});
const onLayoutRootView = useCallback(async () => {
if (fontsLoaded) {
await SplashScreen.hideAsync();
}
}, [fontsLoaded]);
if (!fontsLoaded) {
return <Text style={{ fontFamily: 'Inter-Black', fontSize: 30, color: '#000' }}>A
carregar...</Text>;
}
const Stack = createNativeStackNavigator();
return (
<View onLayout={onLayoutRootView}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Menu" component={Menu}/>
<Stack.Screen
name="FEM Intro"
component={FEMIntro}
/>
</Stack.Navigator>
</NavigationContainer>
</View>
);}
My package.json:
{
"name": "millervillagetycoon",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start --tunnel",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"#react-navigation/native": "^6.1.4",
"#react-navigation/native-stack": "^6.9.10",
"expo": "~47.0.12",
"expo-app-loading": "^2.1.1",
"expo-font": "~11.0.1",
"expo-splash-screen": "~0.17.5",
"expo-status-bar": "~1.4.2",
"react": "18.1.0",
"react-native": "0.70.5",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "~3.18.0",
"react-native-svg-transformer": "^1.0.0",
"react-native-web": "~0.18.9",
"react-dom": "18.1.0",
"#expo/webpack-config": "^0.17.2"
},
"devDependencies": {
"#babel/core": "^7.12.9"
},
"private": true
}
Thank you.

NextJS's Backend service not working when deploying to Docker

So I have made a NextJS App as a frontend and backend server, it works fine when running it locally (by running yarn dev or yarn build then yarn start), but not working on Docker (using the content of Dockerfile from the official Github project). Found out that the whole backend server is not working at all (knew it because I tried to manually access the API outside from the frontend and it all returned NextJS's 404 Not Found Page), what did I do wrong?
Here's the next.config.js:
module.exports = {
output: 'standalone',
async redirects() {
return [
{
source: '/',
destination: `/${process.env.COUNTRY_CODE}/login`,
permanent: false,
basePath: false,
},
{
source: '/login',
destination: `/${process.env.COUNTRY_CODE}/login`,
permanent: false,
basePath: false,
},
]
},
basePath: `/${process.env.COUNTRY_CODE}`,
trailingSlash: true,
env: {
CRMNX_COUNTRY_CODE: process.env.COUNTRY_CODE,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
}
};
my package.json:
{
"name": "myapp",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "node server.js",
"watch": "nodemon server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"#emotion/react": "^11.10.5",
"#emotion/styled": "^11.10.5",
"#headlessui/react": "^1.6.4",
"#heroicons/react": "^1.0.6",
"#mui/icons-material": "^5.10.9",
"#mui/material": "^5.10.12",
"#mui/x-date-pickers": "^5.0.8",
"axios": "^0.27.2",
"bootstrap": "^5.2.2",
"cookie-session": "^2.0.0",
"csurf": "^1.11.0",
"date-format": "^4.0.14",
"dayjs": "^1.11.6",
"express": "^4.18.2",
"express-rate-limit": "^6.4.0",
"express-session": "^1.17.3",
"formik": "^2.2.9",
"iron-session": "^6.3.1",
"jose": "^4.11.1",
"jsonwebtoken": "^8.5.1",
"libphonenumber-js": "^1.10.6",
"moment": "^2.29.4",
"mysql2": "^2.3.3",
"next": "^13.0.6",
"next-auth": "^4.17.0",
"node-cache": "^5.1.2",
"pure-react-carousel": "^1.30.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-device-detect": "^2.2.2",
"react-hook-form": "^7.31.3",
"react-phone-input-2": "^2.15.1",
"sequelize": "^6.25.3",
"sweetalert2": "^11.4.8",
"yup": "^0.32.11"
},
"devDependencies": {
"#babel/preset-react": "^7.18.6",
"autoprefixer": "^10.4.7",
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"eslint": "8.20.0",
"eslint-config-next": "^13.0.6",
"nodemon": "^2.0.20",
"postcss": "^8.4.14",
"prettier": "2.7.1",
"react-select": "^5.5.6",
"sequelize-cli": "^6.5.2",
"tailwindcss": "^3.0.24"
}
}
and my server.js:
const next = require("next");
require('dotenv').config()
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== "production";
const hostname = process.env.APP_HOSTNAME || "localhost";
const app = next({
dev,
hostname,
port,
});
const handle = app.getRequestHandler();
app.prepare().then(() => {
const csrf = require("csurf");
const cookieSession = require("cookie-session");
const server = express();
server.disable("x-powered-by");
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
server.use(
cookieSession({
name: "myapp.sid",
keys: [process.env.APP_KEY],
maxAge: 24 * 60 * 60 * 1000,
})
);
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});
// server.use('/api', limiter)
const router = require("./server/route");
server.use(`/${process.env.COUNTRY_CODE}/api`, router);
server.all("*", (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});

React Native and Expo location on IOS not asking for permissions

I'm working on a React Native app built with Expo and am trying to get the location function to work. While testing it out using Expo Go, I am asked for permissions to use the location feature, but when I build it out an IOS build and test on testflight, I'm never asked for permission. I've tried looking in settings to add the permission, but location isn't even an option.
Where I ask for permission in the app
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
}, []);
}, []);
IOS section of app.json:
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.nawdevelopment.discgolfgames",
"buildNumber": "1.0.4",
"infoPlist":{
"NSLocationUsageDescription":"Disc Golf Games uses location to determine distances, which is used for several games",
"NSLocationWhenInUseUsageDescription":"Disc Golf Games uses location to determine distances, which is used for several games",
"NSLocationAlwaysUsageDescription":"Disc Golf Games uses location to determine distances, which is used for several games",
"NSLocationUsageDescription":"Disc Golf Games uses location to determine distances, which is used for several games",
"UIBackgroundModes": [
"location",
]
}
},
package.json:
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"#react-native-community/masked-view": "^0.1.11",
"#unimodules/core": "~7.2.0",
"#unimodules/react-native-adapter": "~6.5.0",
"ansi-regex": "^4.1.1",
"expo": "^44.0.0",
"expo-linear-gradient": "~11.0.3",
"expo-location": "~14.0.1",
"expo-permissions": "^13.2.0",
"expo-status-bar": "~1.2.0",
"expo-task-manager": "~10.1.0",
"expo-updates": "~0.11.7",
"minimist": "^1.2.6",
"node-fetch": "^2.6.1",
"plist": "^3.0.5",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.3",
"react-native-elements": "^3.4.1",
"react-native-gesture-handler": "~2.1.0",
"react-native-reanimated": "~2.3.1",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1",
"react-native-web": "0.17.1",
"react-navigation": "^4.4.4",
"react-navigation-stack": "^2.10.4",
"react-navigation-tabs": "^2.11.1",
"unimodules-permissions-interface": "^6.1.0",
"url-parse": "1.5.10"
},
"devDependencies": {
"#babel/core": "^7.12.9"
},
"private": true
}
Looking at the anonymous function in your useEffect, it isn't called correctly.
Anonymous functions need () after them to be called. Example:
(function() {
console.log('Hi!');
})();
Source: https://www.javascripttutorial.net/javascript-anonymous-functions/
I took a look at Expo's docs as well and I believe what you are trying to accomplish in your useEffect is the following:
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
})();
}, []); //You have this line twice instead and are missing the line above this one
Source: https://docs.expo.dev/versions/v45.0.0/sdk/location/#usage
Without the () your anonymous function in your useEffect isn't being called, which is probably why you're never asked for permission.
As for your app.json and package.json, it looks like you have everything properly installed. What you should make sure is that in the "plugins" section of app.json you have expo-location showing up.

How to add a unique constraint on type field with Graphql and Neo4j?

I'm making a GRANDStack application so I'm using neo4j and graphql,
I have this type in my schema.graphql file :
type User {
userId: ID!
username: String! #unique
mail: String! #unique
password: String! #private
}
But I'm still able to create multiple accounts with the same
I know this is possible when I look at neo4j documentation here :
https://neo4j.com/docs/graphql-manual/current/directives/#_unique
but the only I found for now is to manually do something like this in my database :
CREATE CONSTRAINT ON (u:User) ASSERT u.mail IS UNIQUE;
But it's not a good idea, I want this to be automatic
I think my package.json is also up to date :
{
"name": "grand-stack-starter-api",
"version": "0.0.1",
"description": "API app for GRANDstack",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start:dev": "./node_modules/.bin/nodemon --watch src --ext js,graphql --exec babel-node src/index.js",
"build": "babel src --out-dir build && shx cp .env build 2>/dev/null || : && shx cp src/schema.graphql build",
"now-build": "babel src --out-dir build && shx cp src/schema.graphql build",
"start": "npm run build && node build/index.js",
"seedDb": "./node_modules/.bin/babel-node src/seed/seed-db.js"
},
"author": "William Lyon",
"license": "MIT",
"dependencies": {
"#apollo/client": "^3.2.5",
"#neo4j/graphql": "^2.4.0",
"apollo-server": "^3.5.0",
"apollo-server-lambda": "^2.19.0",
"csv-parse": "^4.10.1",
"dotenv": "^7.0.0",
"graphql": "^16.0.1",
"neo4j-driver": "^4.4.0",
"node-fetch": "^2.6.0",
"react": "^16.13.1"
},
"devDependencies": {
"#babel/cli": "^7.8.4",
"#babel/core": "^7.9.0",
"#babel/node": "^7.8.7",
"#babel/plugin-proposal-class-properties": "^7.8.3",
"#babel/plugin-transform-runtime": "^7.9.0",
"#babel/preset-env": "^7.9.0",
"#babel/preset-react": "^7.9.4",
"#babel/preset-typescript": "^7.9.0",
"#babel/runtime-corejs3": "^7.9.2",
"babel-plugin-auto-import": "^1.0.5",
"babel-plugin-module-resolver": "^4.0.0",
"cross-env": "^7.0.2",
"nodemon": "^1.19.1",
"shx": "^0.3.2"
}
}
So, turned out the documentation is not updated
To fix this I first had to follow this :
https://neo4j.com/docs/graphql-manual/current/type-definitions/constraints/#type-definitions-constraints-unique
the solution would be to add this which is wrong
await neoSchema.assertConstraints({ options: { create: true }});
I talked with someone working at neo4j in their discord and the right function one :
await neoSchema.assertIndexesAndConstraints({ options: { create: true }});
If like me you can't do await since your code is not in a function you can do something like this with .then :
const neoSchema = new Neo4jGraphQL({
...
})
neoSchema
.assertIndexesAndConstraints({ options: { create: true } })
.then(() => {
const server = new ApolloServer({
...
app.listen({ host, port, path }, () => {
console.log(`GraphQL server ready at http://${host}:${port}${path}`)
})
})

ionic 3 input item shows no caret on iOS

I have an ionic 3 app.
On a page I have a form with some fields.
<form>
<ion-item>
<ion-label>First item</ion-label>
<ion-input type="text" [(ngModel)]="title" name="title"></ion-input>
</ion-item>
... some more simple fields ...
<ion-item>
<ion-label>Item below keyboard region</ion-label>
<ion-textarea [(ngModel)]="description" name="description"></ion-textarea>
</ion-item>
<button ion-button type="submit" block>Add Todo</button>
</form>
When I tab the first, the keyboard is shown and the input item is properly focussed, that is: shows a blinking caret.
Though, when I click a field at a position below the area needed to show the keyboard, I do not get a caret, although the field is actually focussed. When I type, the karakters are put in the field.
The main difference is that when clicking on the lower field, the form is shifted upwards when the keyboard shows.
How to fix this?
I am running the app on an iPad 2017, iOS 11.2.2.
package.json:
{
"name": "my app",
"version": "1.0.1",
"author": "Ionic Framework",
"homepage": "http://ionicframework.com/",
"private": true,
"scripts": {
"clean": "ionic-app-scripts clean",
"build": "ionic-app-scripts build --release",
"lint": "ionic-app-scripts lint",
"ionic:build": "ionic-app-scripts build",
"ionic:serve": "ionic-app-scripts serve"
},
"dependencies": {
"#angular/animations": "5.0.0",
"#angular/common": "5.0.0",
"#angular/compiler": "5.0.0",
"#angular/compiler-cli": "5.0.0",
"#angular/core": "5.0.0",
"#angular/forms": "5.0.0",
"#angular/http": "5.0.0",
"#angular/platform-browser": "5.0.0",
"#angular/platform-browser-dynamic": "5.0.0",
"#ionic-native/app-version": "^4.5.2",
"#ionic-native/calendar": "^4.3.2",
"#ionic-native/call-number": "^4.4.2",
"#ionic-native/camera": "^4.3.2",
"#ionic-native/core": "4.3.0",
"#ionic-native/date-picker": "^4.4.2",
"#ionic-native/file": "^4.4.2",
"#ionic-native/in-app-browser": "^4.3.3",
"#ionic-native/keyboard": "^4.4.2",
"#ionic-native/media-capture": "^4.4.0",
"#ionic-native/native-page-transitions": "^4.3.2",
"#ionic-native/splash-screen": "4.3.0",
"#ionic-native/status-bar": "4.3.0",
"#ionic/pro": "^1.0.9",
"#ionic/storage": "2.0.1",
"#ngx-translate/core": "^9.1.0",
"#ngx-translate/http-loader": "^2.0.1",
"call-number": "^1.0.1",
"com.telerik.plugins.nativepagetransitions": "^0.6.5",
"cordova-ios": "^4.5.4",
"cordova-plugin-app-version": "^0.1.9",
"cordova-plugin-calendar": "^4.6.0",
"cordova-plugin-camera": "^2.4.1",
"cordova-plugin-compat": "^1.2.0",
"cordova-plugin-datepicker": "^0.9.3",
"cordova-plugin-device": "^1.1.7",
"cordova-plugin-file": "^5.0.0",
"cordova-plugin-file-transfer": "^1.7.0",
"cordova-plugin-inappbrowser": "^1.7.2",
"cordova-plugin-ionic-webview": "^1.1.16",
"cordova-plugin-media-capture": "^1.4.3",
"cordova-plugin-privacyscreen": "^0.4.0",
"cordova-plugin-splashscreen": "^4.1.0",
"cordova-plugin-statusbar": "^2.3.0",
"cordova-plugin-whitelist": "^1.3.3",
"cordova-windows": "^5.0.0",
"intl": "^1.2.5",
"ionic-angular": "3.9.2",
"ionic-plugin-keyboard": "^2.2.1",
"ionicons": "3.0.0",
"mx.ferreyra.callnumber": "0.0.2",
"ng2-datepicker": "^2.2.1",
"plist": "^2.1.0",
"rxjs": "5.5.2",
"sw-toolbox": "3.6.0",
"zone.js": "0.8.18"
},
"devDependencies": {
"#ionic/app-scripts": "3.1.0",
"cors": "^2.8.4",
"typescript": "2.4.2",
"ws": "3.3.2"
},
"description": "An Ionic project",
"cordova": {
"plugins": {
"com.telerik.plugins.nativepagetransitions": {},
"cordova-plugin-camera": {
"CAMERA_USAGE_DESCRIPTION": " ",
"PHOTOLIBRARY_USAGE_DESCRIPTION": " "
},
"cordova-plugin-calendar": {
"CALENDAR_USAGE_DESCRIPTION": "This app uses your calendar to plan sessions."
},
"cordova-plugin-privacyscreen": {},
"ionic-plugin-keyboard": {},
"cordova-plugin-whitelist": {},
"cordova-plugin-device": {},
"cordova-plugin-splashscreen": {},
"cordova-plugin-ionic-webview": {},
"cordova-plugin-inappbrowser": {},
"cordova-plugin-media-capture": {
"CAMERA_USAGE_DESCRIPTION": " ",
"MICROPHONE_USAGE_DESCRIPTION": " ",
"PHOTOLIBRARY_USAGE_DESCRIPTION": " "
},
"cordova-plugin-datepicker": {},
"mx.ferreyra.callnumber": {},
"cordova-plugin-statusbar": {},
"call-number": {},
"cordova-plugin-file": {
"PHOTOLIBRARY_USAGE_DESCRIPTION": "This allows",
"PHOTOLIBRARY_ADD_USAGE_DESCRIPTION": "This allows",
"FILE_USAGE_DESCRIPTION": "This app uses your files to upload on sessions.",
"CAMERA_USAGE_DESCRIPTION": " ",
"MICROPHONE_USAGE_DESCRIPTION": " "
},
"cordova-plugin-app-version": {}
},
"platforms": [
"windows",
"ios"
]
}
}
app.module.ts:
...
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
IonicModule.forRoot(MyApp, {scrollAssist: false, autoFocusAssist: 'delay'})
],
in app.component.ts:
this.platform.ready().then(() => {
console.log('Platform is ready!');
this.keyboard.disableScroll(false);
...
thanks!
I have this hackish solution, but I guess there have to be a better one. But because of the comment of user2158259, I'll post it anyway. Please don't punish me for posting it ;-)
1) Remove packages/plugins if you have them:
ionic cordova plugin remove ionic-plugin-keyboard
npm uninstall ionic-plugin-keyboard --save
npm uninstall #ionic-native/keyboard --save
2) Remove any references in app.module.ts such as
this.keyboard.disableScroll(true);
3) create a sub-folder in the app-folder and add these two files:
device.service.ts
The following code is a subset of my device.service.ts
import {ApplicationRef, EventEmitter, Injectable} from '#angular/core';
#Injectable()
export class DeviceService {
public onTick: EventEmitter<string> = new EventEmitter();
private tickValue = new Date().getTime();
constructor(public appRef: ApplicationRef) {
window.addEventListener('onresize', () => {
console.log('DeviceService: on resize');
this.doTick();
setTimeout(() => {
this.doTick();
}, 100);
}, false);
window.addEventListener('transitionend', () => {
console.log('transition ended');
this.doTick();
}, false);
this.tickValue = new Date().getTime();
setTimeout(() => {
this.doTick();
});
}
/**
* getTickValue() returns a different value when something changed (orientation, keyboard up/down).
* by adding this to the screen, the Ionic caret will be adjusted properly
*/
getTickValue(): string {
return this.tickValue + ' ' + window.innerWidth + ' ' + window.innerHeight + ' ' + window.orientation;
}
doTick(): void {
this.tickValue = new Date().getTime();
this.onTick.emit(String(this.tickValue));
this.appRef.tick();
}
}
kb-scroll.ts
import {ApplicationRef, Directive, ElementRef, HostListener, Renderer2} from '#angular/core';
import {DeviceService} from './device.service';
#Directive({
selector: '[kb-scroll]' // Attribute selector
})
export class KbScrollDirective {
constructor(public appRef: ApplicationRef,
public elm: ElementRef,
public renderer: Renderer2,
public device: DeviceService) {
}
#HostListener('click', ['$event'])
onClick($event) {
let elmClickedY;
let scrollContent: HTMLElement;
if ('TEXTAREA' === this.elm.nativeElement.tagName) {
scrollContent = $event.toElement;
elmClickedY = $event.offsetY;
} else if ('ION-CONTENT' === this.elm.nativeElement.tagName) {
// calculate Y offset between click and top of scroll-content area
scrollContent = this.elm.nativeElement.querySelector('.scroll-content');
if (scrollContent) {
// $event.offsetY is most likely small offset in clicked input field in scroll content
// calculate the offsetY opposed to the container (div.scroll-content)
let clickScreenY = $event.toElement.getBoundingClientRect().top + $event.offsetY;
let scrollContentScreenY = scrollContent.getBoundingClientRect().top;
elmClickedY = clickScreenY - scrollContentScreenY;
} else {
console.warn('KbScrollDirective: could not find .scroll-content div in ', this.elm.nativeElement);
}
} else {
console.warn('KbScrollDirective: Can\'t handle ', this.elm.nativeElement.tagName);
}
//TODO: OK to 'RE-ASSIGN' window.onresize ?
window.onresize = () => {
if (scrollContent) {
setTimeout(() => {
let elmHeight = scrollContent.clientHeight;
if (elmClickedY > elmHeight) {
let toScroll = elmClickedY - elmHeight;
scrollContent.scrollTop += toScroll + 40;
this.device.doTick();
}
}, 100);
}
}
}
}
4) Add this service and directive to you module
5) Use the kb-scroll directive on your <ion-content> HTML tag:
<ion-content kb-scroll>
... your form here ...
</ion-content>
6) now comes the really dirty part (and it will become clear why we need the service).
It seems that the repaint of the Ionic caret is triggered when something changes on the screen, so we need to force that.
I added a <span> to my HTML template that holds the root <ion-nav> tag:
app.html
<!-- invisible ticker, needed to get Keyboard caret into place -->
<div style="position: absolute; top:-100px">{{tickValue}}</div>
<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false" class="root-content"></ion-nav>
7) Add this code in your app.component.ts:
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
...
tickValue: string;
constructor(...,
public device: DeviceService) {
this.device.onTick.subscribe(tickValue => {
this.tickValue = tickValue;
});
}
So whenever the DeviceService#tickValue get's updated, this subscriber will update the app component's tick-value, causing a repaint of the screen, although the tick value is rendered outside the visible area of the screen. This seems to cause the Ionic caret to be positioned correctly.
Bonus: this also works for a textarea that takes more place than half the screen (= screen height - keyboard height). Just add the kb-scroll directive to your <textarea> HTML tag.
Please note this is a very hackish solution.
Any comments/improvements are really welcome!

Resources