Iām trying to make work Akavache in a Windows Universal Application (8.1 for now, using ReactiveUI 6.5).
To make sure that it is not related to my architecture, I did an empty solution that has all the necessary packages and requirements (VC++ for both platforms), and I still get the same issue. This is a blocker for me since I want all my queries to be cached.
Here's the code:
BlobCache.ApplicationName = "MyApp"; // In AppBootstrapper`
// In My ViewModel
SubmitCommand = ReactiveCommand.CreateAsyncTask(async _ =>
{
var isTrue = await BlobCache.UserAccount.GetOrFetchObject("login_credentials",
async () => await Task.FromResult(true)
);
// My Code never goes further than this
if (!isTrue)
{
throw new Exception("I'm false!");
}
return isTrue;
});
SubmitCommand.Subscribe(isTrue => {
Debug.WriteLine("I got a new value!");
});
SubmitCommand.ThrownExceptions.Subscribe(ex => {
UserError.Throw(ex.Message, ex);
});
// In The View
ViewModel = new MainPageViewModel();
this.BindCommand(ViewModel, x => x.SubmitCommand, x => x.SubmitCommand);
public MainPageViewModel ViewModel
{
get { return (MainPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MainPageViewModel), typeof(MainPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainPageViewModel)value; }
}
Edit After some debug, Windows Phone 8.1 Silverlight works, not Jupiter.
So what's missing?
I'm using RXUI 6.5 (latest) with a Windows Phone 8.1 (Jupiter) (with shared Universal Projects)
Updated: Akavache.Sqlite3 is causing the issue. InMemoryCache is working (removing Akavache.Sqlite3 "fixes" the problem), but not Sqlite3.
Also, registering BlobCache's different types of cache (copy paste from https://github.com/akavache/Akavache/blob/3c1431250ae94d25cf7ac9637528f4445b131317/Akavache.Sqlite3/Registrations.cs#L32) is working apparently.. so I suppose the Registration class aren't working properly and calling
new Akavache.Sqlite3.Registrations().Register(Locator.CurrentMutable); is not working.
Edit: My temporary solution is to copy paste this into my application, and I invoke it after BlobCache.ApplicationName. It works, but I shouldn't technically have to do that.
Thanks for your help
Related
Seems like a simple question, but I haven't been able to find a simple answer. Essentially I want to choose which page in the app to start on based on some stored state. I added a GoToAsync call in the AppShell constructor, but this didn't work--which makes sense because the AppShell hasn't been fully constructed yet.
I found this answer, but it feels like it kind of skirts around the issue:
Maui AppShell - Navigate on Open
Where is the best place to inject some code that will run once on startup and can successfully navigate a .NET Maui app to a chosen page?
After playing around with overrides, it seems like overriding Application.OnStart works! Shell.Current is set at this point and navigation works.
Here's additional code that allows for asynchronous initialization and uses a Loading Page until the initialization is complete:
using MyApp.Services;
using MyApp.UI;
namespace MyApp;
public partial class App : Application
{
ConfigurationProviderService m_configProvider;
public App(ConfigurationProviderService configProvider)
{
m_configProvider = configProvider;
InitializeComponent();
MainPage = new LoadingPage();
}
protected override void OnStart()
{
var task = InitAsync();
task.ContinueWith((task) =>
{
MainThread.BeginInvokeOnMainThread(() =>
{
MainPage = new AppShell();
// Choose navigation depending on init
Shell.Current.GoToAsync(...);
});
});
base.OnStart();
}
private async Task InitAsync()
{
await m_configProvider.InitAsync();
}
}
The code below was working until around preview 11 of Maui. But with preview 13 I get a compiler error: 'EntryHandler' does not contain a definition for 'EntryMapper'.
A similar error is shown for PickerMapper.
The code has been copied from the official documentation.
#if ANDROID
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
#endif
using Application = Microsoft.Maui.Controls.Application;
namespace myapp;
public partial class App : Application
{
public App(AuthenticationService authenticationService, SyncService syncService)
{
InitializeComponent();
// Remove underline from all pickers and entries in app
#if ANDROID
Microsoft.Maui.Handlers.PickerHandler.PickerMapper.AppendToMapping("NoUnderline", (h, v) =>
{
h.NativeView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
});
Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping("NoUnderline", (h, v) =>
{
h.NativeView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
});
#endif
MainPage = new AppShell(authenticationService, syncService);
}
}
Anyone who sees the solution?
I have been searching high and low, but the consensus seems to be that the code is correct.
I think all the EntryMapper and PickerMapper, basically all {Control}Mapper got renamed to just Mapper.
Additionally, I see you have references to NativeView whenever you upgrade to preview 14, those will be renamed to PlatformView so you will have to rename those accordingly as well.
Chatting was working perfectly before migrating to null safety using signalr. But after migrating It is not working in chatting part.
Scenario is like there are 2 screens where I am using signalr.
1)Chatlist.
2)Chatting with person.
listener in Chatlist is perfect but in 2nd screen it is not working(Just worked when I installed and run for the 1st time). Weird issue.
All was working in old. I am using bloc for statemanagement and also migrated to yield to emit.
Piece of code is like:
void listenOnMessageReceived(
HubConnection hubConnection,
Function(Message? chatMessageReceive) onMessageReceived,
) {
final SocketResponseCallBack chatMessageReceived =
(response) => onMessageReceived(Message.fromJson(response));
final hubMethod = HubMethod(
CHAT_RECEIVED_MESSAGE_METHOD_NAME,
SignalRHelper.toSocketFunction(
CHAT_RECEIVED_MESSAGE_METHOD_NAME, chatMessageReceived));
bool exists = listenOnHubMethod.any((method) => method.methodName == CHAT_RECEIVED_MESSAGE_METHOD_NAME);
if(exists) {
listenOnHubMethod.removeWhere((element) =>
element.methodName == CHAT_RECEIVED_MESSAGE_METHOD_NAME);
SignalRHelper(hubConnection: hubConnection).on(
hubMethod.methodName,
hubMethod.methodFunction,
);
listenOnHubMethod.add(hubMethod);
}else{
SignalRHelper(hubConnection: hubConnection).on(
hubMethod.methodName,
hubMethod.methodFunction,
);
listenOnHubMethod.add(hubMethod);
}
}
I am having 2 types of above code in different screens. but it is working in only 1 screen and not listening in 2nd screen.
here is a piece of signalr listener code:
static MethodInvocationFunc toSocketFunction(
String methodName, SocketResponseCallBack responseCallBack) {
return (arguments) {
try {
if (arguments!.isEmpty) {
throw SocketEmptyResponseException(methodName);
}
final response = arguments.first;
responseCallBack(response);
} on FormatException {
throw SocketResponseException(methodName);
}
};
}
Is there any limitations in migration of stable version or anything else. Every help is appreciable.
Thank you.
Do not use signalR, on IOS it will be impossible to run listener on background or when app is closed and you will miss messages. Use FCM.
I am developing a GTK application in GJS and like to reuse parts of the GTK
code inside a Gnome Shell extension. However, I did not find a way to add a
Gtk.Widget to the menu of my Gnome Shell panel icon.
I tried to use GtkClutter.Actor from clutter-gtk, but the library seems to
be out-dated and does neither work in a Wayland or X11 Gnome Shell, since it
requires Clutter 1.0 but sees 7 already loaded. When importing
imports.gi.GtkClutter in an extension, Gnome Shell yields this error:
Requiring GtkClutter, version none: Requiring namespace 'Clutter' version '1.0', but '7' is already loaded.
Here is some code to demonstrate that clutter-gtk actually works, if directly
running it via gjs; probably because I can enforce GTK 3.0 here.
gtkclutter.js:
imports.gi.versions.Gtk = '3.0' // fails if set to 4.0
const { Gtk, GLib, Clutter, GtkClutter } = imports.gi
// gtkUI returns a Gtk.Widget tree. This should be the reusable code.
function gtkUI() {
return new Gtk.Label({
label: '<span size="100000">š</span>',
use_markup: true,
})
}
// embedClutterActor returns a Gtk.Widget with an embedded Clutter.Actor.
function embedClutterActor(clutter_actor) {
let embed = new GtkClutter.Embed()
embed.get_stage().add_child(clutter_actor)
return embed
}
// embedGtkWidget returns a Clutter.Actor with an embedded Gtk.Widget.
function embedGtkWidget(gtk_widget) {
return new GtkClutter.Actor({ contents: gtk_widget })
}
class App {
constructor() {
this.title = 'GtkClutter'
GLib.set_prgname(this.title)
}
onActivate() { this.window.show_all() }
onStartup() { this.buildUI() }
run(ARGV=[]) {
this.app = new Gtk.Application()
this.app.connect('activate', () => this.onActivate())
this.app.connect('startup', () => this.onStartup())
this.app.run(ARGV)
}
buildUI() {
let w = this.window = new Gtk.ApplicationWindow({
application: this.app, title: this.title, icon_name: 'face-smile',
default_height: 160, default_width: 160, window_position: Gtk.WindowPosition.CENTER,
})
// Just to demonstrate that GtkClutter embedding works, we use both embeds here to create
// a Gtk.Widget from a Clutter.Actor from the actual Gtk.Widget that we want to show.
GtkClutter.init(null)
Clutter.init(null)
w.add(embedClutterActor(embedGtkWidget(gtkUI())))
// In the actual GTK App, we would just have used `w.add(gtkUI())`
// and not imported Clutter and GtkClutter at all.
}
}
new App().run(ARGV)
Here is the companion extension to the GTK app, trying (and failing)
to reuse the GTK code as contents of a GtkClutter.Actor.
extension.js:
const { Clutter, Gtk, Gio, St } = imports.gi
let GtkClutter = null // lazy import for debugging
const Main = imports.ui.main
const PanelMenu = imports.ui.panelMenu
const PopupMenu = imports.ui.popupMenu
const Me = imports.misc.extensionUtils.getCurrentExtension()
const VERSION = 'dev-version' // filled during install
const NAME = 'GtkClutterExt'
// gtkUI returns a Gtk.Widget tree. This should be the reusable code.
function gtkUI() {
return new Gtk.Button({ child: Gtk.Label({
label: `<span size="100000">š</span>`,
use_markup: true,
})})
}
// stUI returns an Gnome Shell widget tree that works only in Gnome Shell.
function stUI(icon_name='face-sad') {
return new St.Icon({ icon_name })
}
function statusIcon(icon_name) {
let box = new St.BoxLayout()
let icon = new St.Icon({ icon_name, style_class: 'system-status-icon emotes-icon' })
box.add_child(icon)
box.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM))
return box
}
class Ext {
constructor() { this.panel_widget = null }
enable() {
log(`enabling extension ${Me.uuid}`)
try {
// Use St only for the status icon and the menu container (not the menu content).
let btn = this.panel_widget = new PanelMenu.Button(0.0, NAME, false)
let item = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false })
btn.menu.addMenuItem(item)
Main.panel.addToStatusArea(NAME, btn)
try { GtkClutter = imports.gi.GtkClutter }
catch (e) { log(`failed to load clutter-gtk, err=${e.message}`) }
if (GtkClutter) {
// Using St for the status icon is OK, since it is only used by the extension.
btn.add_child(statusIcon('face-happy'))
// But for the menu, I like to reuse my GTK code from the GTK app.
// That is what GtkClutter was designed for, I believe.
item.actor.add_child(new GtkClutter.Actor({ contents: gtkUI() }))
} else {
// As fallback we show our mood regarding GtkClutter support in Gnome Shell ;)
btn.add_child(statusIcon('face-sad'))
item.actor.add_child(stUI('face-angry'))
}
} catch (e) {
log(`failed to enable ${Me.uuid}, err=${e.message}`)
}
}
disable() {
debug(`disabling extension ${Me.uuid}`)
if (this.panel_widget == null) return
this.panel_widget.destroy()
this.panel_widget = null
}
}
function init() { return new Ext() }
I know that clutter-gtk is quite dated (see https://gitlab.gnome.org/GNOME/clutter-gtk),
but I did not find a better way to lift GTK into my extension.
Questions
Does Gnome Shell provide something similar to GtkClutter.Actor that allows
extension programmers to reuse their GJS/GTK code?
Which alternative way to reuse GTK/GJS code do you see?
If GTK is such a universal and cross-platform library, why does Gnome Shell not
support it out-of-the box? (Bonus question š, more out of curiosity)
TL;DR You can not use GTK widgets in a GNOME Shell extension.
The toolkit used in GNOME Shell is Clutter, not GTK. Clutter is an internal library of Mutter, while GTK3 is only used in GNOME Shell for a handful of utilities.
Clutter used to be a standalone library, but is now developed specifically as a compositor toolkit for Mutter. GTK is an application toolkit, not suited for use in a compositor.
The standalone Clutter project is effectively unmaintained now, making GtkClutter pretty much the same.
I am investigating using Ionic 4/ Capacitor to target Windows via the Electron option, for an application where I want to use SQLite.
Using the Ionic Native SQLite plugin, which wraps this Cordova plugin, out of the box, as far as I can see, the Windows support is for UWP, and not Desktop, which runs using Electron in Ionic Capacitor wrapper.
My plan, was to see if I could use Electron SQLite package, and then call this from my Ionic application by making a wrapper class for the Ionic native similar to what I used to get browser support by following this tutoral
If I can call the Electron code from my Ionic app, then I can't see why this wouldn't work.
So, my question here is, can I call code (I will add functions to use the SQlite) I add to the hosting Electron application from within the Ionic (web) code? And if so, how?
Thanks in advance for any help
[UPDATE1]
Tried the following...
From an Ionic page, I have a button click handler where I raise an event..
export class HomePage {
public devtools() : void {
let emit = new EventEmitter(true);
emit.emit('myEvent');
var evt = new CustomEvent('myEvent');
window.dispatchEvent(evt);
}
Then within the Electron projects index.js, I tried..
mainWindow.webContents.on('myEvent', () => {
mainWindow.openDevTools();
});
const ipc = require('electron').ipcMain
ipc.on('myEvent', (ev, arg) => {
mainWindow.openDevTools();
});
But neither worked.
I should mention I know very little about Electron. This is my first exposure to it (via Capacitor)
In case someone is interested, this is how I solved this.
Im am using Ionic 4 / Capacitor + Vue 3.
In my entry file (app.ts) I have declared a global interface called Window as follows:
// app.ts
declare global { interface Window { require: any; } }
Then, I have written the following class:
// electron.ts
import { isPlatform } from '#ionic/core';
export class Electron
{
public static isElectron = isPlatform(window, 'electron');
public static getElectron()
{
if (this.isElectron)
{
return window.require('electron');
}
else
{
return null;
}
}
public static getIpcRenderer()
{
if (this.isElectron)
{
return window.require('electron').ipcRenderer;
}
else
{
return null;
}
}
public static getOs()
{
if (this.isElectron)
{
return window.require('os');
}
else
{
return null;
}
}
}
And I use it like this:
//electronabout.ts
import { IAbout } from './iabout';
import { Plugins } from '#capacitor/core';
import { Electron } from '../utils/electron';
export class ElectronAbout implements IAbout
{
constructor() { }
public async getDeviceInfo()
{
let os = Electron.getOs();
let devInfo =
{
arch: os.arch(),
platform: os.platform(),
type: os.type(),
userInfo: os.userInfo()
};
return devInfo;
}
public async showDeviceInfo()
{
const devInfo = await this.getDeviceInfo();
await Plugins.Modals.alert({ title: 'Info from Electron', message: JSON.stringify(devInfo) });
}
}
This is working but, of course, I still need to refactor the Electron class (electron.ts). Probably using the singleton pattern is a better idea.
I hope this helps.
Update
You can communicate from the render process with your main process (index.js) like this:
//somefile.ts
if (Electron.isElectron)
{
let ipc = Electron.getIpcRenderer();
ipc.once('hide-menu-button', (event) => { this.isMenuButtonVisible = false; });
}
//index.js
let newWindow = new BrowserWindow(windowOptions);
newWindow.loadURL(`file://${__dirname}/app/index.html`);
newWindow.webContents.on('dom-ready', () => {
newWindow.webContents.send('hide-menu-button');
newWindow.show();
});
I dug into this yesterday and have an example for you using angular(this should apply to ionic too).
in your service declare require so we can use it
//Below your imports
declare function require(name:string);
Then in whatever function you want to use it in:
// Require the ipcRenderer so we can emit to the ipc to call a function
// Use ts-ignore or else angular wont compile
// #ts-ignore
const ipc = window.require('electron').ipcRenderer;
// Send a message to the ipc
// #ts-ignore
ipc.send('test', 'google');
Then in the created index.js within the electron folder
// Listening for the emitted event
ipc.addListener('test', (ev, arg) => {
// console.log('ev', ev);
console.log('arg', arg);
});
Its probably not the correct way to access it but its the best way i could find. From my understanding the ipcRenderer is used for when you have multiple browsers talking to each other within electron. so in our situation it enables our web layer to communicate with the electron stuff