Browse Source

added android shared preference settings accessor

pull/2/head
Skycoder42 6 years ago
parent
commit
96826f988e
No known key found for this signature in database GPG Key ID: 8E01AD9EF0578D2B
  1. 17
      examples/mvvmcore/DroidSettings/DroidSettings.pro
  2. 21
      examples/mvvmcore/DroidSettings/main.cpp
  3. 69
      examples/mvvmcore/DroidSettings/main.qml
  4. 5
      examples/mvvmcore/DroidSettings/qml.qrc
  5. 37
      examples/mvvmcore/DroidSettings/settings.cpp
  6. 31
      examples/mvvmcore/DroidSettings/settings.h
  7. 2
      examples/mvvmcore/SampleCore/SampleCore.pro
  8. 3
      examples/mvvmcore/mvvmcore.pro
  9. 1
      src/imports/mvvmcore/qtmvvmcore_plugin.cpp
  10. 13
      src/jar/jar.pro
  11. 110
      src/jar/src/de/skycoder42/qtmvvm/core/AndroidSettingsAccessor.java
  12. 263
      src/mvvmcore/androidsettingsaccessor.cpp
  13. 51
      src/mvvmcore/androidsettingsaccessor.h
  14. 30
      src/mvvmcore/androidsettingsaccessor_p.h
  15. 13
      src/mvvmcore/mvvmcore.pro
  16. 7
      src/mvvmdatasynccore/datasyncsettingsentry.cpp
  17. 4
      src/src.pro

17
examples/mvvmcore/DroidSettings/DroidSettings.pro

@ -0,0 +1,17 @@
TEMPLATE = app
QT += quick mvvmcore
TARGET = DroidSettings
SOURCES += \
main.cpp \
settings.cpp
RESOURCES += qml.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/mvvmcore/$$TARGET
INSTALLS += target
HEADERS += \
settings.h

21
examples/mvvmcore/DroidSettings/main.cpp

@ -0,0 +1,21 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "settings.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Settings settings;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("settings"), &settings);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}

69
examples/mvvmcore/DroidSettings/main.qml

@ -0,0 +1,69 @@
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Android Settings Test")
Pane {
anchors.fill: parent
GridLayout {
anchors.fill: parent
columns: 6
TextField {
id: keyField
placeholderText: qsTr("key")
Layout.fillWidth: true
Layout.columnSpan: 3
}
TextField {
id: valueField
placeholderText: qsTr("value")
Layout.fillWidth: true
Layout.columnSpan: 3
}
Button {
id: addButton
text: qsTr("save")
Layout.fillWidth: true
Layout.columnSpan: 2
onClicked: settings.save(keyField.text, valueField.text)
}
Button {
id: loadButton
text: qsTr("load")
Layout.fillWidth: true
Layout.columnSpan: 2
onClicked: valueField.text = settings.load(keyField.text)
}
Button {
id: removeButton
text: qsTr("remove")
Layout.fillWidth: true
Layout.columnSpan: 2
onClicked: settings.remove(keyField.text)
}
Label {
id: eventLabel
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.fillHeight: true
Connections {
target: settings
onChangeEvent: eventLabel.text = eventLabel.text + text + "\n";
}
}
}
}
}

5
examples/mvvmcore/DroidSettings/qml.qrc

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>

37
examples/mvvmcore/DroidSettings/settings.cpp

@ -0,0 +1,37 @@
#include "settings.h"
Settings::Settings(QObject *parent) :
QObject{parent},
_accessor{new QtMvvm::AndroidSettingsAccessor{}}
{
connect(_accessor, &QtMvvm::AndroidSettingsAccessor::entryChanged,
this, &Settings::entryChanged);
connect(_accessor, &QtMvvm::AndroidSettingsAccessor::entryRemoved,
this, &Settings::entryRemoved);
}
QString Settings::load(const QString &key)
{
return _accessor->load(key, tr("<unset>")).toString();
}
void Settings::save(const QString &key, const QString &value)
{
_accessor->save(key, value);
}
void Settings::remove(const QString &key)
{
_accessor->remove(key);
}
void Settings::entryChanged(const QString &key, const QVariant &value)
{
emit changeEvent(tr("Data for key <%1> changed to: %2")
.arg(key, value.toString()));
}
void Settings::entryRemoved(const QString &key)
{
emit changeEvent(tr("Data for key <%1> removed").arg(key));
}

31
examples/mvvmcore/DroidSettings/settings.h

@ -0,0 +1,31 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <QObject>
#include <QtMvvmCore/AndroidSettingsAccessor>
class Settings : public QObject
{
Q_OBJECT
public:
explicit Settings(QObject *parent = nullptr);
Q_INVOKABLE QString load(const QString &key);
public Q_SLOTS:
void save(const QString &key, const QString &value);
void remove(const QString &key);
Q_SIGNALS:
void changeEvent(const QString &text);
private Q_SLOTS:
void entryChanged(const QString &key, const QVariant &value);
void entryRemoved(const QString &key);
private:
QtMvvm::AndroidSettingsAccessor *_accessor;
};
#endif // SETTINGS_H

2
examples/mvvmcore/SampleCore/SampleCore.pro

@ -35,5 +35,3 @@ DISTFILES += $$TRANSLATIONS
target.path = $$[QT_INSTALL_EXAMPLES]/mvvmcore/$$TARGET
INSTALLS += target
samples_in_build: QMAKE_QSETTINGSTRANSLATOR = $$PWD/../../../bin/qsettingstranslator.py

3
examples/mvvmcore/mvvmcore.pro

@ -2,4 +2,5 @@ TEMPLATE = subdirs
QT_FOR_CONFIG += core
SUBDIRS += \
SampleCore
SampleCore \
DroidSettings

1
src/imports/mvvmcore/qtmvvmcore_plugin.cpp

@ -5,6 +5,7 @@
#include <QtMvvmCore/ViewModel>
#include <QtMvvmCore/SettingsViewModel>
#include <QtMvvmCore/Messages>
#include <QtMvvmCore/ISettingsAccessor>
#include "qqmlmvvmbinding.h"
#include "qqmlmvvmmessage.h"

13
src/jar/jar.pro

@ -0,0 +1,13 @@
TARGET = QtMvvmCore
load(qt_build_paths)
CONFIG += java
DESTDIR = $$MODULE_BASE_OUTDIR/jar
JAVACLASSPATH += $$PWD/src
JAVASOURCES += $$PWD/src/de/skycoder42/qtmvvm/core/AndroidSettingsAccessor.java
# install
target.path = $$[QT_INSTALL_PREFIX]/jar
INSTALLS += target

110
src/jar/src/de/skycoder42/qtmvvm/core/AndroidSettingsAccessor.java

@ -0,0 +1,110 @@
package de.skycoder42.qtmvvm.core;
import java.util.Set;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
class AndroidSettingsAccessor {
private class Listener implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(address != 0 && preferences == sharedPreferences)
callback(address, key);
}
}
long address = 0;
SharedPreferences preferences = null;
SharedPreferences.Editor editor = null;
Listener listener = new Listener();
private static native void callback(long address, Object key);
public AndroidSettingsAccessor(Context context, long address) {
this.address = address;
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(listener);
}
public AndroidSettingsAccessor(Context context, String name, int mode, long address) {
this.address = address;
preferences = context.getSharedPreferences(name, mode);
preferences.registerOnSharedPreferenceChangeListener(listener);
}
public void unref() {
address = 0;
preferences.unregisterOnSharedPreferenceChangeListener(listener);
listener = null;
sync();
}
public boolean contains(String key) {
return preferences.contains(key);
}
public Object load(String key) {
return preferences.getAll().get(key);
}
public boolean save(String key, boolean value) {
boolean res = beginEdit();
editor.putBoolean(key, value);
return res;
}
public boolean save(String key, int value) {
boolean res = beginEdit();
editor.putInt(key, value);
return res;
}
public boolean save(String key, long value) {
boolean res = beginEdit();
editor.putLong(key, value);
return res;
}
public boolean save(String key, float value) {
boolean res = beginEdit();
editor.putFloat(key, value);
return res;
}
public boolean save(String key, String value) {
boolean res = beginEdit();
editor.putString(key, value);
return res;
}
public boolean save(String key, Set<String> value) {
boolean res = beginEdit();
editor.putStringSet(key, value);
return res;
}
public boolean remove(String key) {
boolean res = beginEdit();
editor.remove(key);
return res;
}
public void sync() {
if(editor != null) {
editor.apply();
editor = null;
}
}
private boolean beginEdit() {
if(editor != null)
return false;
else {
editor = preferences.edit();
return true;
}
}
}

263
src/mvvmcore/androidsettingsaccessor.cpp

@ -0,0 +1,263 @@
#include "androidsettingsaccessor.h"
#include "androidsettingsaccessor_p.h"
#include <QtCore/QDataStream>
#include <QtCore/QSet>
#include <QtAndroidExtras/QtAndroid>
#include <QtAndroidExtras/QAndroidJniExceptionCleaner>
#include "qtmvvm_logging_p.h"
using namespace QtMvvm;
AndroidSettingsAccessor::AndroidSettingsAccessor(QObject *parent) :
ISettingsAccessor(parent),
d{new AndroidSettingsAccessorPrivate{this}}
{}
AndroidSettingsAccessor::AndroidSettingsAccessor(const QString &file, QObject *parent) :
AndroidSettingsAccessor{file, Private, parent}
{}
AndroidSettingsAccessor::AndroidSettingsAccessor(const QString &file, Mode mode, QObject *parent) :
ISettingsAccessor(parent),
d{new AndroidSettingsAccessorPrivate{this, file, mode}}
{}
AndroidSettingsAccessor::~AndroidSettingsAccessor()
{
if(d->settings.isValid()) {
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
d->settings.callMethod<void>("unref");
}
}
bool AndroidSettingsAccessor::contains(const QString &key) const
{
if(!d->settings.isValid())
return false;
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
return d->settings.callMethod<jboolean>("contains",
"(Ljava/lang/String;)Z",
QAndroidJniObject::fromString(key).object());
}
QVariant AndroidSettingsAccessor::load(const QString &key, const QVariant &defaultValue) const
{
if(!d->settings.isValid())
return defaultValue;
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
auto value = d->settings.callObjectMethod("load", "(Ljava/lang/String;)Ljava/lang/Object;",
QAndroidJniObject::fromString(key).object());
if(value.isValid())
return d->convertFromJava(value);
else
return defaultValue;
}
void AndroidSettingsAccessor::save(const QString &key, const QVariant &value)
{
if(!d->settings.isValid())
return;
bool needSync = false;
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
QVariant strVar;
switch(value.userType()) {
case QMetaType::Bool:
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;Z)Z",
QAndroidJniObject::fromString(key).object(),
static_cast<jboolean>(value.toBool()));
break;
case QMetaType::Int:
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;I)Z",
QAndroidJniObject::fromString(key).object(),
static_cast<jint>(value.toInt()));
break;
case QMetaType::LongLong:
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;J)Z",
QAndroidJniObject::fromString(key).object(),
static_cast<jlong>(value.toLongLong()));
break;
case QMetaType::Float:
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;F)Z",
QAndroidJniObject::fromString(key).object(),
static_cast<jfloat>(value.toFloat()));
break;
default:
if(value.userType() == qMetaTypeId<QSet<QString>>()) {
auto set = value.value<QSet<QString>>();
QAndroidJniObject hashSet{"java/util/HashSet", "(I)V", static_cast<jint>(set.size())};
for(const auto &item : set) {
hashSet.callMethod<jboolean>("add", "(Ljava/lang/Object;)Z",
QAndroidJniObject::fromString(item).object());
}
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;Ljava/util/Set;)Z",
QAndroidJniObject::fromString(key).object(),
hashSet.object());
break;
} else {
auto ok = false;
if(value.canConvert(QMetaType::QString) &&
QVariant{static_cast<int>(QMetaType::QString)}.canConvert(value.userType())) {
strVar = value;
ok = strVar.convert(QMetaType::QString);
}
if(!ok) {
QByteArray data;
QDataStream stream{&data, QIODevice::WriteOnly};
stream << value;
strVar = QStringLiteral("__qtmvvm_variant<%1>{%2}")
.arg(stream.version())
.arg(QString::fromUtf8(data.toBase64()));
}
Q_FALLTHROUGH();
}
case QMetaType::QString:
needSync = d->settings.callMethod<jboolean>("save", "(Ljava/lang/String;Ljava/lang/String;)Z",
QAndroidJniObject::fromString(key).object(),
QAndroidJniObject::fromString((strVar.isValid() ? strVar : value).toString()).object());
break;
}
if(needSync)
QMetaObject::invokeMethod(this, "sync", Qt::QueuedConnection);
emit entryChanged(key, value);
}
void AndroidSettingsAccessor::remove(const QString &key)
{
if(!d->settings.isValid())
return;
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
auto needSync = d->settings.callMethod<jboolean>("remove", "(Ljava/lang/String;)Z",
QAndroidJniObject::fromString(key).object());
if(needSync)
QMetaObject::invokeMethod(this, "sync", Qt::QueuedConnection);
emit entryRemoved(key);
}
void AndroidSettingsAccessor::sync()
{
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
d->settings.callMethod<void>("sync");
}
void AndroidSettingsAccessor::changeCallback(const QString &key)
{
auto value = load(key, {});
if(contains(key))
emit entryChanged(key, load(key));
else
emit entryRemoved(key);
}
// ------------- private implementation -------------
AndroidSettingsAccessorPrivate::AndroidSettingsAccessorPrivate(AndroidSettingsAccessor *q_ptr) :
q{q_ptr}
{
setup();
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
settings = QAndroidJniObject {
"de/skycoder42/qtmvvm/core/AndroidSettingsAccessor",
"(Landroid/content/Context;J)V",
QtAndroid::androidContext().object(),
reinterpret_cast<jlong>(this)
};
}
AndroidSettingsAccessorPrivate::AndroidSettingsAccessorPrivate(AndroidSettingsAccessor *q_ptr, const QString &file, AndroidSettingsAccessor::Mode mode) :
q{q_ptr}
{
setup();
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
settings = QAndroidJniObject {
"de/skycoder42/qtmvvm/core/AndroidSettingsAccessor",
"(Landroid/content/Context;Ljava/lang/String;IJ)V",
QtAndroid::androidContext().object(),
QAndroidJniObject::fromString(file).object(),
static_cast<jint>(mode),
reinterpret_cast<jlong>(this)
};
}
void AndroidSettingsAccessorPrivate::setup()
{
varRegex = QRegularExpression {
QStringLiteral(R"__(^__qtmvvm_variant<(\d+)>{(.*)}$)__"),
QRegularExpression::OptimizeOnFirstUsageOption | QRegularExpression::DotMatchesEverythingOption
};
}
QVariant AndroidSettingsAccessorPrivate::convertFromJava(const QAndroidJniObject &object)
{
QAndroidJniExceptionCleaner cleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
QAndroidJniEnvironment env;
auto stringClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/lang/String"));
auto intClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/lang/Integer"));
auto longClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/lang/Long"));
auto floatClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/lang/Float"));
auto boolClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/lang/Boolean"));
auto setClass = QAndroidJniObject::fromLocalRef(env->FindClass("java/util/Set"));
if(env->IsInstanceOf(object.object(), intClass.object<jclass>()))
return object.callMethod<jint>("intValue","()I");
else if(env->IsInstanceOf(object.object(), longClass.object<jclass>()))
return object.callMethod<jlong>("longValue","()J");
else if(env->IsInstanceOf(object.object(), floatClass.object<jclass>()))
return object.callMethod<jfloat>("floatValue","()F");
else if(env->IsInstanceOf(object.object(), boolClass.object<jclass>()))
return QVariant::fromValue<bool>(object.callMethod<jboolean>("booleanValue","()Z"));
else if(env->IsInstanceOf(object.object(), setClass.object<jclass>())) {
QSet<QString> set;
set.reserve(object.callMethod<jint>("size"));
auto iterator = object.callObjectMethod("iterator", "()Ljava/util/Iterator;");
if(!iterator.isValid())
return {};
while(iterator.callMethod<jboolean>("hasNext"))
set.insert(iterator.callObjectMethod("next", "()Ljava/lang/Object;").toString());
return QVariant::fromValue(set);
} else if(env->IsInstanceOf(object.object(), stringClass.object<jclass>())) {
auto str = object.toString();
auto match = varRegex.match(str);
if(match.hasMatch()) {
auto data = QByteArray::fromBase64(match.captured(2).toUtf8());
QDataStream stream{data};
stream.setVersion(match.captured(1).toInt());
QVariant result;
stream.startTransaction();
stream >> result;
if(stream.commitTransaction())
return result;
else
return {};
} else
return str;
} else {
logWarning() << "Unknown JAVA-Type in shared preferences";
return {};
}
}
void AndroidSettingsAccessorPrivate::dataChangedCallback(const QString &key)
{
auto value = q->load(key, {});
if(q->contains(key))
emit q->entryChanged(key, q->load(key));
else
emit q->entryRemoved(key);
}
extern "C" {
JNIEXPORT void JNICALL Java_de_skycoder42_qtmvvm_core_AndroidSettingsAccessor_callback(JNIEnv */*env*/, jobject /*obj*/, jlong address, jobject key)
{
auto self = reinterpret_cast<AndroidSettingsAccessorPrivate*>(address);
QMetaObject::invokeMethod(self->q, "changeCallback", Qt::QueuedConnection,
Q_ARG(QString, QAndroidJniObject{key}.toString()));
}
}

51
src/mvvmcore/androidsettingsaccessor.h

@ -0,0 +1,51 @@
#ifndef QTMVVM_ANDROIDSETTINGSACCESSOR_H
#define QTMVVM_ANDROIDSETTINGSACCESSOR_H
#include <QtCore/qscopedpointer.h>
#include "QtMvvmCore/qtmvvmcore_global.h"
#include "QtMvvmCore/isettingsaccessor.h"
namespace QtMvvm {
class AndroidSettingsAccessorPrivate;
class Q_MVVMCORE_EXPORT AndroidSettingsAccessor : public ISettingsAccessor
{
Q_OBJECT
Q_INTERFACES(QtMvvm::ISettingsAccessor)
public:
enum ModeFlag {
Private = 0x00000000,
WorldReadable = 0x00000001,
WorldWritable = 0x00000002,
MultiProcess = 0x00000004
};
Q_DECLARE_FLAGS(Mode, ModeFlag)
Q_FLAG(Mode)
explicit AndroidSettingsAccessor(QObject *parent = nullptr);
explicit AndroidSettingsAccessor(const QString &file, QObject *parent = nullptr);
explicit AndroidSettingsAccessor(const QString &file, Mode mode, QObject *parent = nullptr);
~AndroidSettingsAccessor() override;
bool contains(const QString &key) const override;
QVariant load(const QString &key, const QVariant &defaultValue = {}) const override;
void save(const QString &key, const QVariant &value) override;
void remove(const QString &key) override;
public Q_SLOTS:
void sync() override;
private Q_SLOTS:
void changeCallback(const QString &key);
private:
QScopedPointer<AndroidSettingsAccessorPrivate> d;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(QtMvvm::AndroidSettingsAccessor::Mode)
#endif // QTMVVM_ANDROIDSETTINGSACCESSOR_H

30
src/mvvmcore/androidsettingsaccessor_p.h

@ -0,0 +1,30 @@
#ifndef QTMVVM_ANDROIDSETTINGSACCESSOR_P_H
#define QTMVVM_ANDROIDSETTINGSACCESSOR_P_H
#include <QtCore/QRegularExpression>
#include <QtAndroidExtras/QAndroidJniObject>
#include "androidsettingsaccessor.h"
namespace QtMvvm {
class AndroidSettingsAccessorPrivate
{
public:
AndroidSettingsAccessor *q;
QAndroidJniObject settings;
QRegularExpression varRegex;
AndroidSettingsAccessorPrivate(AndroidSettingsAccessor *q_ptr);
AndroidSettingsAccessorPrivate(AndroidSettingsAccessor *q_ptr, const QString &file, AndroidSettingsAccessor::Mode mode);
void setup();
QVariant convertFromJava(const QAndroidJniObject &object);
void dataChangedCallback(const QString &key);
};
}
#endif // QTMVVM_ANDROIDSETTINGSACCESSOR_P_H

13
src/mvvmcore/mvvmcore.pro

@ -41,6 +41,19 @@ SOURCES += \
settingsentry.cpp \
settingsconfigloader.cpp
android {
QT += androidextras
HEADERS += \
androidsettingsaccessor.h \
androidsettingsaccessor_p.h
SOURCES += \
androidsettingsaccessor.cpp
ANDROID_BUNDLED_JAR_DEPENDENCIES = \
jar/QtMvvmCore.jar
}
include(../settingsconfig/settingsconfig.pri)
TRANSLATIONS += \

7
src/mvvmdatasynccore/datasyncsettingsentry.cpp

@ -1,10 +1,15 @@
#include "datasyncsettingsentry.h"
#include <QtCore/QDataStream>
#include <QtMvvmCore/private/qtmvvm_logging_p.h>
#if QT_HAS_INCLUDE(<optional>) && __cplusplus >= 201703L
#include <optional>
#define QTMVVM_HAS_OPTIONAL
#endif
#undef logDebug
#undef logInfo
#undef logWarning
#undef logCritical
#include <QtMvvmCore/private/qtmvvm_logging_p.h>
using namespace QtMvvm;
namespace QtMvvm {

4
src/src.pro

@ -9,6 +9,10 @@ mvvmwidgets.depends += mvvmcore
mvvmquick.depends += mvvmcore
imports.depends += mvvmcore mvvmquick
android {
SUBDIRS += jar
}
qtHaveModule(datasync) {
SUBDIRS += mvvmdatasynccore \
mvvmdatasyncwidgets \

Loading…
Cancel
Save