I play with NativeScript on Wear OS2.
I just took the NS HelloWorld (typescript non frameworks) and I added the Firebase plugin to get a simple text from the notification and display it in textbox.
All okay, except when the app resumes from background, in that case the UI does not render the text change.
Debugging, I see the notification payload coming up to my typescript page instance, no errors... But the UI doesn't render it.
If I run the same app on a phone, all works as expected also resuming from background. Same code, no one line difference.
I am wondering if NativeScript does support Wear OS...
Any thoughts about that?
Thanks, Fabio.
Some code...
//main-page.ts
export function navigatingTo(args: EventData) {
//here Firebase is already init, all right...
(<Page>args.object).bindingContext = (new ViewModel).init();
}
//view-model.ts
import { Observable } from "tns-core-modules/data/observable";
import { NotificationService } from "./notification-service";
export class ViewModel extends Observable {
private _message: string = "Waiting message...";
init(): ViewModel {
//this callback is always invoked as expected
//also when the app is killed and comes back to life
NotificationService.onMessage = (firebaseMessage: any) => {
const text = firebaseMessage['text'] || firebaseMessage['data']['text'];
console.log('text' + text);
this.message = text; //that's okay, see setter below
}
return this;
}
get message(): string {
return this._message;
}
set message(value: string) {
if (this._message !== value) {
this._message = value;
//to this point the UI should update... BUT
//it works only IF the app is in foreground
//AND was never killed/backgrounded before.
//(bug on wear-os only)
this.notifyPropertyChange("message", value);
}
}
}
//main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<StackLayout class="p-20">
<Label text="{{ message }}" class="h2 text-center" textWrap="true" />
</StackLayout>
</Page>
Related
I am following the Microsoft tutorial for creating an Application Customizer. That works great, but now I would like to add some custom things like a curtain menu to my site.
I can't seem to figure out how to add an event listener to an element that is being rendered from a promise. The element doesn't exist initially, so if I try the ID, I get a null error. If I use a class it gives me an inline script error.
I've searched for a solution, but don't see one that applies to what I am doing.
I know this code is a mess, but I've been trying so many different methods, I can't seem to find one that works.
import { override } from '#microsoft/decorators';
import { Log } from '#microsoft/sp-core-library';
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName,
PlaceholderProvider
} from '#microsoft/sp-application-base';
import { Dialog } from '#microsoft/sp-dialog';
import * as strings from 'HideSideNavApplicationCustomizerStrings';
// import * as strings from './myStrings';
import styles from './AppCustomizer.module.scss';
import {escape} from '#microsoft/sp-lodash-subset';
import Placeholder from '#microsoft/sp-application-base/lib/extensibility/placeholder/Placeholder';
const LOG_SOURCE: string = 'HideSideNavApplicationCustomizer';
let openBtn = "openBtn";
export interface IHideSideNavApplicationCustomizerProperties {
// This is an example; replace with your own property
testMessage: string;
Top: string;
openBtn: string;
}
/** A Custom Action which can be run during execution of a Client Side Application */
export default class HideSideNavApplicationCustomizer
extends BaseApplicationCustomizer<IHideSideNavApplicationCustomizerProperties> {
private _topPlaceholder: PlaceholderContent | undefined;
#override
public onInit(): Promise<void> {
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
return Promise.resolve();
}
private _renderPlaceHolders(): void {
console.log('calling _renderPlaceHolders');
console.log(
"Available placeholders: ",
this.context.placeholderProvider.placeholderNames
.map(name => PlaceholderName[name])
.join(", ")
);
if(!this._topPlaceholder){
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this.onDispose}
)
}
if(!this._topPlaceholder) {
console.error("The expected placeholder (Top) was not found.");
return;
}
if(this.properties){
let topString: string = `
<!-- The overlay -->
<div id="myNav" class="navClose overlay">
<!-- Button to close the overlay navigation -->
×
<!-- Overlay content -->
<div class="overlay-content">
About
Services
Clients
Contact
</div>
</div>
<!-- Use any element to open/show the overlay navigation menu -->
<span class="navOpen">open</span>
`;
if(!topString){
topString = "(Top property was not defined.)";
}
if(this._topPlaceholder.domElement){
this._topPlaceholder.domElement.innerHTML=topString;
let navState :string = "closed";
const navClose = document.getElementsByClassName("navClose").item(0);
this._topPlaceholder.domElement.addEventListener("click", function(e){
if(navState == "closed"){
navClose.setAttribute(this.style.width, "100%");
navState = "opened";
}
else{
navClose.setAttribute(this.style.width, "0");
navState = "closed";
}
});
}
}
}
private _onDispose(): void {
console.log('[HideSideNavApplicationCustomizer._onDispose] Dispose custom top and bottom.')
}
}
So I think I finally figured this out. I wasn't using 'this' correctly after the promise was made, and there were a number of syntax errors with how I was trying to update the attributes on the elements.
Here's my updated code in case anyone is trying to do something similar.
import { override } from '#microsoft/decorators';
import { Log } from '#microsoft/sp-core-library';
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName,
PlaceholderProvider
} from '#microsoft/sp-application-base';
import styles from './AppCustomizer.module.scss';
const LOG_SOURCE: string = 'HideSideNavApplicationCustomizer';
let openBtn = "openBtn";
export interface IHideSideNavApplicationCustomizerProperties {
// This is an example; replace with your own property
testMessage: string;
Top: string;
}
/** A Custom Action which can be run during execution of a Client Side Application */
export default class HideSideNavApplicationCustomizer
extends BaseApplicationCustomizer<IHideSideNavApplicationCustomizerProperties> {
private _topPlaceholder: PlaceholderContent | undefined;
#override
public onInit(): Promise<void> {
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
return Promise.resolve();
}
private _renderPlaceHolders(): void {
console.log('calling _renderPlaceHolders');
console.log(
"Available placeholders: ",
this.context.placeholderProvider.placeholderNames
.map(name => PlaceholderName[name])
.join(", ")
);
if(!this._topPlaceholder){
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this.onDispose}
)
}
if(!this._topPlaceholder) {
console.error("The expected placeholder (Top) was not found.");
return;
}
if(this.properties){
let topString: string = `
<!-- The overlay -->
<div id="nav" class="${styles.overlay}">
<!-- Button to close the overlay navigation -->
×
<!-- Overlay content -->
<div class="${styles['overlay-content']}">
About
Services
Clients
Contact
</div>
</div>
<!-- Use any element to open/show the overlay navigation menu -->
<span id="navOpen">open</span>
`;
if(!topString){
topString = "(Top property was not defined.)";
}
if(this._topPlaceholder.domElement){
const top = this._topPlaceholder.domElement;
top.innerHTML=topString;
let nav = top.querySelector('#nav');
let navOpen = top.querySelector('#navOpen');
let navClose = top.querySelector('#navClose');
navOpen.addEventListener("click", () => {
nav.setAttribute("style","width:75%;");
});
navClose.addEventListener("click", () => {
nav.setAttribute("style","width:0%;");
});
}
}
}
private _onDispose(): void {
console.log('[HideSideNavApplicationCustomizer._onDispose] Dispose custom top and bottom.')
}
}
So I am banging my head, I realized my stand along Watch App had a STUPID long name of "App Name - WatchKit App" so I went into my Target and changed the Display Name to "App Name" removing WatchKit App. Well now my app won't validate when uploading to the Appstore. I get the message - Invalid Info.plist key. The key WKExtensionDelegateClassName in bundle App Name.app/Watch/App Name WatchKit App.app is invalid.
My Info.plist has the value of
<key>WKExtensionDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
I have confirmed that I have #WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate in my #main for the SwiftUI App. And when I print a few values in my app launch I get the following confirmations:
Super Init - ExtensionDelegate
Contentview
applicationDidFinishLaunching for watchOS
Super Init - ExtensionDelegate
Optional(Wasted_Time_Watch_Extension.MeetingSetup)
Optional(Wasted_Time_Watch_Extension.MeetingStatistics)
Optional(Wasted_Time_Watch_Extension.Meeting)
applicationDidBecomeActive for watchOS
update complication
I create three classes at launch and print this in the log with print(ExtensionDelegate.shared.Setup as Any) , etc. The other lines are just confirming where I am at app startup.
This is a WatchOS8 application and I am running Xcode version Version 13.1 (13A1030d).
Update - Here's the entry in my plist
<key>WKExtensionDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
<key>WKWatchOnly</key>
And my App code
import SwiftUI
#if os(watchOS)
import ClockKit
#endif
struct DelegateKey: EnvironmentKey {
typealias Value = ExtensionDelegate
static let defaultValue: ExtensionDelegate = ExtensionDelegate()
}
extension EnvironmentValues {
var extensionDelegate: DelegateKey.Value {
get {
return self[DelegateKey.self]
}
set {
self[DelegateKey.self] = newValue
}
}
}
#main
struct WastedTimeWatchApp: App {
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
let prefs: UserDefaults = UserDefaults(suiteName: suiteName)!
#SceneBuilder var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
.environment(\.extensionDelegate, delegate)
}
}
}
}
class ExtensionDelegate: NSObject, WKExtensionDelegate, ObservableObject {
#Environment(\.extensionDelegate) static var shared
// variables removed to simplify posting
override init() {
print("Super Init - ExtensionDelegate")
super.init()
}
func applicationDidFinishLaunching() {
print("applicationDidFinishLaunching for watchOS")
ExtensionDelegate.shared.meetingSetup = MeetingSetup()
print(ExtensionDelegate.shared.meetingSetup as Any)
ExtensionDelegate.shared.meetingStatistics = MeetingStatistics()
print(ExtensionDelegate.shared.meetingStatistics as Any)
ExtensionDelegate.shared.meeting = Meeting()
print(ExtensionDelegate.shared.meeting as Any)
}
func applicationDidBecomeActive() {
print("applicationDidBecomeActive for watchOS")
print("update complication")
let server = CLKComplicationServer.sharedInstance()
for complication in server.activeComplications ?? [] {
server.reloadTimeline(for: complication)
}
}
func applicationDidBecomeInactive() {
print("update complication")
let server = CLKComplicationServer.sharedInstance()
for complication in server.activeComplications ?? [] {
server.reloadTimeline(for: complication)
}
print("applicationDidBecomeInactive for watchOS")
}
}
I figured this out... I had duplicated the plist entry in both the WatchKit App and WatchKit Extension plist file. Removed it from the list WatchKit Extension plist and all is working fine.
The following code shows an iOS 13 SwiftUI Toggle example. It runs on a device (iPhone XR), but shows an error in the log when the toggle is tapped. I only observe this on a device, not the live preview.
import SwiftUI
struct ContentView: View {
#State private var foo = false
var body: some View {
Form{
Toggle(isOn: $foo, label: {
Text("Label")
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The error is:
2019-09-23 12:59:01.468146-0500 Demo[640:40285] invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.
Any advice is appreciated.
This error also occurs for me with a toggle on the iPhone 8 simulator.
No crash experienced.
The cells having the toggles would not properly resize when moved, but this was avoided by adding a Bool and toggling it as part of the cell move function { eg, triggerRefresh.toggle() } This unexpected user behavior may or may not be related to the toggle error.
Same as C K,
No crash experienced.
I got the value of the switch using a separate Bool var
var stateOfSwitch = false
And used a separate #IBAction for the switch like so..
#IBAction func toggled(_ sender: UISwitch) {
stateOfSwitch = ! stateOfSwitch
}
Remember to link the IBAction to the switch element in your interface builder.
And use the stateOfSwitch variable to implement any logic you have to implement based on the switch.
I am aware this is a work around, hoping this won't be needed soon.
Not sure how to do this in SwiftUI.
I have a Xamarin.Form MainPage:ContentPage with a ToolbarItems Bar on top. The toolbar items are bound to my ViewModel like so:
<ToolbarItem Text="Sync" Command="{Binding ReloadCommand}" >
</ToolbarItem>
The availability of the Item depends on some logic:
private bool canReloadExecute(object arg)
{
bool result = (!IsReloading && (App.GetPersistentSetting("DeviceID") != "") && (App.GetPersistentSetting("BuildingID") != ""));
return result;
}
There is a separate dialog controlling the DeviceID and BuildingID on a different settings page. Once any of those ids is entered it is persistently stored away
App.SetPersistentSetting("DeviceID",value);
Problem is, that the menu items don't change their appearance once my code uses popAsync() to return to the Main page. I need to restart my app to see the changes. According to the debugger, canReloadExecute isn't called. Why this?
What I tried to work around this issue is to force a refresh in the MainPage's OnAppearing method like this:
public void RefreshToolbarItems()
{
TestApp.ViewModels.MainViewModel mvm = (TestApp.ViewModels.MainViewModel)BindingContext;
mvm.RefreshToolbarItems();
}
... and in the ViewModel:
public void RefreshToolbarItems()
{
OnPropertyChanged("BuildingScanCommand");
OnPropertyChanged("ReloadCommand");
}
but this code runs through but changes nothing, while the Debugger shows that the routine is indeed firing the events, they seem to go nowhere.
Any ideas how I can get my menu going?
Edit 1: "Show command initalization"
I am not shre what specifically you mean, but here is the whole code dealing with the command:
private ICommand _reloadCommand;
public ICommand ReloadCommand => _reloadCommand ?? (_reloadCommand = new Command(ExecuteReloadCommand, canReloadExecute));
private bool _isReloading = false;
public bool IsReloading
{
get => _isReloading;
set
{
_isReloading = value;
((Command)_reloadCommand).ChangeCanExecute();
OnPropertyChanged(nameof(ReloadCommand));
}
}
private bool canReloadExecute(object arg)
{
bool result = (!IsReloading && (App.GetPersistentSetting("DeviceID") != "") && (App.GetPersistentSetting("BuildingID") != ""));
return result;
}
private async void ExecuteReloadCommand(object obj)
{
IsReloading = true;
// Some code ...
IsReloading = false;
}
The goal is to disable the command, if either the command handler is already running, or if the configuration of DeviceID and/or BuildingId hasn't been done yet.
The enable/disable does almost work, if I set the DeviceId and BuildingId, and restart the app, the command is properly enabled or disabled. It doesn't work, however, if I set the Ids in a sub-page and return to the main page.
meanwhile I came to the conclusion, that firing onPropertyChange obviously doesn't make the command check its canReloadExecute. So the question is, how do I trigger this?
I finally solved the issue myself, this code works nicely for me:
public void RefreshToolbarItems()
{
((Command)BuildingScanCommand).ChangeCanExecute();
((Command)ReloadCommand).ChangeCanExecute();
}
I am trying to write an application, but it is constantly crashing when using the uiimagepickercontroller. I thought that it might be because I was not disposing of the picker after each use, but it will often freeze up on first run as well. Usually I'll take a picture and it just freezes, never asking to "use" the picture.
Do you have any suggestions? Here is my code. Has anyone gotten this to work?
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
myPicker = new UIImagePickerController();
myPicker.Delegate = new myPickerDelegate(this);
myAlbumButton.Clicked += delegate {
if(UIImagePickerController.IsSourceTypeAvailable(UIImagePickerControllerSourceType.PhotoLibrary)){
myPicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
myPicker.AllowsEditing = true;
this.PresentModalViewController (myPicker, true);
}else{
Console.WriteLine("cannot get album");
}
};
myCameraButton.Clicked += delegate {
if(UIImagePickerController.IsSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
myPicker.SourceType = UIImagePickerControllerSourceType.Camera;
//myPicker.AllowsEditing = true;
this.PresentModalViewController (myPicker, true);
}else{
Console.WriteLine("cannot get camera");
}
};
}
private class myPickerDelegate : UIImagePickerControllerDelegate
{
private TestView _vc;
public myPickerDelegate ( TestView controller):base()
{
_vc = controller;
}
public override void FinishedPickingImage (UIImagePickerController myPicker, UIImage image, NSDictionary editingInfo)
{
// TODO: Implement - see: http://go-mono.com/docs/index.aspx?link=T%3aMonoTouch.Foundation.ModelAttribute
_vc.myImageView.Image = image;
myPicker.DismissModalViewControllerAnimated(true);
}
}
Try to call your event handlers code from the main thread by using BeginInvokeOnMainThread().
So my issue was very similar.
Instead of having a delegate class, I had the delegates inline for the picker.
For some reason the app froze every time after talking the image, not stopping in any breakpoint after that.
The solution that worked for me was to use this book:
http://www.scribd.com/doc/33770921/Professional-iPhone-Programming-with-MonoTouch-and-NET-C