I can't find option to build apk from xamarin project in Rider IDE. Thanks
It is not possible directly (or maybe in 2019.3, I haven't updated yet), but as far as producing release version, with consistent / automatically updated build number, zip aligned and signed apk, You can use an extra tool, like for example cake, that could leverage MSBuild and handle all the process of increasing the build number, cleaning / restoring the nugget packages, building/signing/ZipAlignin the apk, running tests, uploading the apk to your server/google.
Here is a example of such a Cake file:
#addin "Cake.AndroidAppManifest"
#addin nuget:?package=Cake.Git
#tool nuget:?package=NUnit.ConsoleRunner&version=3.9.0
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var SolutionFile = "../MyProject.sln";
var projectToBuild = "./MyProject.Droid.csproj";
var buildDir = Directory("./bin") + Directory(configuration);
var objDir = Directory("./obj") + Directory(configuration);
var pkg = "com.organisation.app";
var AndroidSDK = "/Users/Me/Library/Developer/Xamarin/android-sdk-macosx";
var AndroidBuildToolsVersion = "28.0.3";
int rc = 0;
// the build number will be set to the commit number. First commit, bn = 1,...
var buildNumber = GitLog(Directory(".."), int.MaxValue).Count;
Task("Clean")
.Does(() =>
{
CleanDirectory(buildDir);
CleanDirectories(objDir);
});
Task("Restore")
.IsDependentOn("Clean")
.Does(() =>
{
NuGetRestore(SolutionFile);
});
Task("Increase build number")
.IsDependentOn("Restore")
.Does(() =>
{
var pathToAndroidManifest = "./Properties/AndroidManifest.xml";
var manifest = DeserializeAppManifest(new FilePath(pathToAndroidManifest));
manifest.VersionCode = buildNumber;
SerializeAppManifest(new FilePath(pathToAndroidManifest), manifest);
});
Task("Build")
.IsDependentOn("Increase build number")
.Does(() =>
{
MSBuild("../MyProject.sln", new MSBuildSettings().SetConfiguration(configuration));
});
Task("Tests")
.IsDependentOn("Build")
.Does(() =>
{
});
Task("package")
.IsDependentOn("Tests")
.Does(() =>
{
// Creates the initial build of an apk.
MSBuild(projectToBuild, s => {
s.SetVerbosity(Verbosity.Quiet);
s.SetPlatformTarget(PlatformTarget.MSIL);
s.SetConfiguration(configuration);
s.WithTarget("SignAndroidPackage");
s.WithProperty("SolutionDir", "../");
});
});
Task("zipAlign")
.IsDependentOn("package")
.Does(() =>
{
Information("Zip-aligning APK");
rc = StartProcess($"{AndroidSDK}/build-tools/{AndroidBuildToolsVersion}/zipalign", new ProcessSettings {
Arguments = new ProcessArgumentBuilder()
.Append("-f")
.Append("-v 4")
.Append($"{buildDir}/{pkg}-Signed.apk")
.Append($"{buildDir}/{pkg}.apk")
});
if (rc == 0) {
Information("APK Zip Alignment succeeded.");
} else {
var msg = "APK Zip Alignment failure.";
throw new Exception(msg);
}
});
Task("Signing")
.IsDependentOn("zipAlign")
.Does(() =>
{
var keystore = $"../keystore/release.keystore";
var pass = "*********";
Information("Signing apk...");
rc = StartProcess($"{AndroidSDK}/build-tools/{AndroidBuildToolsVersion}/apksigner", new ProcessSettings {
Arguments = new ProcessArgumentBuilder()
.Append("sign")
.Append("--ks")
.Append(keystore)
.Append($"--ks-pass pass:{pass}")
.Append($"{buildDir}/{pkg}.apk")
});
if (rc != 0) {
var msg = "APK Signing failed.";
throw new Exception(msg);
}
});
Task("Tag")
.IsDependentOn("Signing")
.Does(() => {
Information("Adding a github tag with the build number");
GitTag(Directory(".."), $"{buildNumber}");
});
Task("Publish")
.IsDependentOn("Tag")
.Does(() =>
{
Information("Publishing with FastLane");
var jsonFile = $"../keystore/apiKey.json";
var apk = $"{buildDir}/{pkg}.apk";
Information("Publishing to google play with fastlane supply");
rc = StartProcess("fastlane", new ProcessSettings {
Arguments = new ProcessArgumentBuilder()
.Append("supply")
.Append("--json_key")
.Append(jsonFile)
.Append("--package_name")
.Append(pkg)
.Append("--apk")
.Append(apk)
.Append("--track")
.Append("beta")
});
});
Task("Default")
.IsDependentOn("Publish");
RunTarget(target);
Related
The code below works running the app through >ionic serve. Click on select will pop up a file selection box, select the image and everything else works. However when I debug with Android Studio and run it on my Android phone, the reader.onloaded never triggered. I can't verify but i believe event.target.files was empty as well after I selected several photos on the phone and proceed. I had READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permission added in AndroidManifest.xml.
---- HTML ----
<a (click)="fileInput.click()">select</a>
<input #fileInput style='display: none' type="file" accept="image/jpeg" multiple (change)="selectFiles($event)" />
--- Angular ----
selectFiles(event: any): void {
this.message = [];
this.progressInfos = [];
this.selectedFiles = event.target.files;
if (this.selectedFiles && this.selectedFiles[0]) {
const numberOfFiles = this.selectedFiles.length;
if (numberOfFiles > 8){
alert("Max 8 images");
}else{
for (let i = 0; i < numberOfFiles; i++) {
var reader = new FileReader();
reader.onloadend = (e: any) => {
const image = new Image();
image.src = e.target.result;
image.onload = () => {
let index = this.previews.findIndex(preview=>preview.img === '');
this.previews[index] = {img:e.target.result, width:image.width, height:image.height, data:this.selectedFiles[i], inProgress: false, progress: 0, id: null};
this.fileSelectTrigger.notify(true);
};
};
reader.readAsDataURL(this.selectedFiles[i]);
}
}
}
}
I'm currently using the nativescript-mediafilepicker to upload files. I want the user to pick files regardless of the file extension but the only file shwon by the application is a jpg file. What seems to be the problem here?
public openCustomFilesPicker() {
let extensions = [];
if (Application.ios) {
extensions = [kUTTypePDF, kUTTypeText];
} else {
extensions = ["txt", "pdf", "jpg"];
}
let options: FilePickerOptions = {
android: {
extensions: extensions,
maxNumberFiles: 2,
},
ios: {
extensions: extensions,
multipleSelection: true,
hostView: this._hostView,
},
};
let mediafilepicker = new Mediafilepicker();
mediafilepicker.openFilePicker(options);
mediafilepicker.on("getFiles", function (res) {
let results = res.object.get("results");
console.dir(results);
if (results) {
for (let i = 0; i < results.length; i++) {
let result = results[i];
console.log(result.file);
}
}
});
mediafilepicker.on("error", function (res) {
let msg = res.object.get("msg");
console.log(msg);
});
mediafilepicker.on("cancel", function (res) {
let msg = res.object.get("msg");
console.log(msg);
});
}
I have a problem with this reference From this Site Using Google SpreadSheet as Database. the problem is that. the Android Application cannot fetch the data in a google spreadsheet. The Application can successfully run but couldn't find data in the Spreadsheet.
I Already Updated the codes from the android studio because of the old version used codes. I just copy and paste it and change some of it.
Codes Below:
The Script Link to fetch the Data :
https://script.google.com/macros/s/AKfycbwZJCWoQ7dpC5KwyRM9JYsjCjymQUspAfPmniOApD_CSEoc-LdP/exec?id=16O_OfgKxASgqa2WWQKePJI1jnJMTdb4OyXbUJU6kWH0&sheet=Sheet1
Link of the SpreadSheet :
https://docs.google.com/spreadsheets/d/16O_OfgKxASgqa2WWQKePJI1jnJMTdb4OyXbUJU6kWH0/edit#gid=0
This is the App Script that Fetches the data:
you can also see the app script codes here : App Script Codes.
function doGet(request) {
var output = ContentService.createTextOutput(),
data = {},
id = request.parameters.id,
sheet = request.parameters.sheet,
ss = SpreadsheetApp.openById(id);
data.records = readData_(ss, sheet);
var callback = request.parameters.callback;
if (callback === undefined) {
output.setContent(JSON.stringify(data));
} else {
output.setContent(callback + "(" + JSON.stringify(data) + ")");
}
output.setMimeType(ContentService.MimeType.JSON);
return output;
}
function readData_(ss, sheetname, properties) {
if (typeof properties == "undefined") {
properties = getHeaderRow_(ss, sheetname);
properties = properties.map(function(p) { return p.replace(/\s+/g, '_'); });
}
var rows = getDataRows_(ss, sheetname),
data = [];
for (var r = 0, l = rows.length; r < l; r++) {
var row = rows[r],
record = {};
for (var p in properties) {
record[properties[p]] = row[p];
}
data.push(record);
}
return data;
}
function getDataRows_(ss, sheetname) {
var sh = ss.getSheetByName(sheetname);
return sh.getRange(2, 1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
}
function getHeaderRow_(ss, sheetname) {
var sh = ss.getSheetByName(sheetname);
return sh.getRange(1, 1, 1, sh.getLastColumn()).getValues()[0];
}
More Codes From the Link Above or click here for not scrolling back.
I am trying to set a new track and upload APK for publishing using nodejs... But no APK is uploaded nor is there a track set.
But the results from the validation and all the other steps are exactly as I expect(according to API). The script succeeds, yet nothing changes in the developer console... So is there a magic guru out there that may see my issue?
NOTE 1: I did publish my app manually because the android-publisher is only for updating apps that have been published before
NOTE 2: This worked before(a long time ago), and has stopped working for a while, I finally got around to updating everything
My code:
var google = require('googleapis').google;
var Promise = require('bluebird');
var _ = require('lodash');
var settings = require('./config/settings.json');
// Enable API access into the Developer Console: https://play.google.com/apps/publish/?account=7639196906174529268#ApiAccessPlace
// Create a service account
// Download the JSON and save it here
// Make sure the email of the JSON is added to the apps for release manager role:
// https://play.google.com/apps/publish/?account=7639196906174529268#AdminPlace
var key = require('./config/google-play-user.json');
// editing "scope" allowed for OAuth2
var scopes = [
'https://www.googleapis.com/auth/androidpublisher'
];
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, scopes, null);
var play = google.androidpublisher({
version: 'v3',
auth: oauth2Client,
params: {
// default options
// this is the package name for your initial app you've already set up on the Play Store
packageName: settings.app.id
}
});
google.options({auth: oauth2Client});
// Start with a new edit.
startEdit().then(function(data) {
// Load our APK(in this case a Cordova APK).
var standardAPK = require('fs').readFileSync('./platforms/android/build/outputs/apk/android-release.apk');
// Stage the upload (doesn't actually upload anything).
return upload({
edit: data.edit,
apk: standardAPK,
key: 'standardApk'
});
}).then(function(data) {
// Set our track.
return setTrack(data);
}).then(function(data) {
// Validate our changes.
return validateToPlayStore(data);
}).then(function(data) {
console.log('Successful uploaded APK files:', data);
}).catch(function(err) {
console.log(err);
process.exit(1);
});
/**
* Sets our authorization token and begins an edit transaction.
*/
function startEdit() {
return new Promise(function(resolve, reject) {
jwtClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
process.exit(1);
return;
}
// Set the credentials before we doing anything.
oauth2Client.setCredentials(tokens);
play.edits.insert({
packageName: settings.app.id
}, function(err, edit) {
if (err || !edit) { reject(err); }
resolve({
edit: edit.data
});
});
});
});
}
/**
* Stages an upload of the APK (but doesn't actually upload anything)
*/
function upload(data) {
var edit = data.edit;
var apk = data.apk;
var key = data.key;
return new Promise(function(resolve, reject) {
play.edits.apks.upload({
editId: edit.id,
packageName: settings.app.id,
media: {
mimeType: 'application/vnd.android.package-archive',
body: apk
}
}, function(err, res) {
if (err || !res) { reject(err); }
// Pass any data we care about to the next function call.
var obj = {};
obj[key] = res.data;
resolve(_.omit(_.extend(data, obj), 'apk'));
});
});
}
/**
* Sets our track (beta, production, etc.)
*/
function setTrack(data) {
var edit = data.edit;
var track = 'production';
return new Promise(function(resolve, reject) {
play.edits.tracks.update({
editId: edit.id,
track: track,
packageName: settings.app.id
}, function(err, res) {
if (err || !res) { reject(err); }
resolve(_.extend(data, {setTrackResults: res.data}));
});
});
}
/**
* Validates our edit transaction and makes our changes live.
*/
function validateToPlayStore(data) {
return new Promise(function(resolve, reject) {
// play.edits.commit({ // Commit will set the change LIVE
play.edits.validate({ // Validate will only validate it, not set it LIVE.
editId: data.edit.id,
packageName: settings.app.id
}, function(err, res) {
if (err || !res) { reject(err); }
resolve(_.extend(data, {validateToPlayStoreResults: res.data}));
});
});
}
Originally from http://frontendcollisionblog.com/javascript/2015/12/26/using-nodejs-to-upload-app-to-google-play.html when it still worked
You can use apkup to upload APKs to Google Play. apkup is basically a friendly wrapper/CLI for the googleapis package.
CLI example:
npx apkup \
--key ./config/google-play-user.json \
--release-notes "en-US=lorem ipsum dolor" \
--apk ./platforms/android/build/outputs/apk/android-release.apk
Node.js example:
const { Apkup } = require('apkup')
const key = require('./config/google-play-user.json')
const apkup = new Apkup(key)
apkup
.upload('./platforms/android/build/outputs/apk/android-release.apk', {
releaseNotes: [
{
language: 'en-US',
text: 'Minor bug fixes...'
}
]
})
.then(data => {
console.log(` > ${data.packageName} version ${data.versionCode} is up!`)
})
I am writing a Cordova plugin for android that has a Gradle dependency.
I want the developers to be able to specify what version of the dependency they want without editing the plugin directly.
Right now I have this in my plugin.xml:
<hook type="before_plugin_install" src="modifyPluginXML.js" />
<framework src="resources/custom.gradle" custom="true" type="gradleReference"/>
<framework src="$TO_BE_DEFINED_PACKAGE" type="gradleReference"/>
and I have a hook that takes the command line argument and replaces $TO_BE_DEFINED_PACKAGE in the plugin.xml with the package path/name provided in the argument.
Here is the modifyPluginXML.js hook:
module.exports = function(context) {
var Q = context.requireCordovaModule('q');
var deferral = new Q.defer();
var fs = require('fs'),
xml2js = require('xml2js'),
path = require('path'),
util = require('util');
var parser = new xml2js.Parser({explicitArray:false});
var cb = function(data){
console.log("plugin.xml updated");
deferral.resolve();
}
fs.readFile(__dirname + '/plugin.xml', function(err, data) {
parser.parseString(data, function (err, result) {
//console.log(util.inspect(result, false, null));
var externalDep = "";
for (var i = 0; i < process.argv.length;i++){
if(process.argv[i].indexOf('EXTERNAL_DEP') >= 0){
externalDep = process.argv[i].replace("EXTERNAL_DEP=", "");
console.log(externalDep);
}
}
result.plugin.platform.framework[1]['$'].src = externalDep;
var builder = new xml2js.Builder();
var xml = builder.buildObject(result);
var filepath = path.normalize(path.join(__dirname, '/plugin.xml'));
fs.writeFile(filepath, xml, cb);
});
});
return deferral.promise;
}
As of right now if you add the plugin with cordova plugin add plugin-name EXTERNAL_DEP=5.0 it will correctly replace the framework src in the plugin.xml with the source specified in the command line argument.
The problem I am running into is that cordova doesn't seem to care about the new plugin.xml. It still uses the old plugin.xml's framework tags.
In the generate build.gradle file I still see this:
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
debugCompile project(path: "CordovaLib", configuration: "debug")
releaseCompile project(path: "CordovaLib", configuration: "release")
compile "$TO_BE_DEFINED_PACKAGE"
// SUB-PROJECT DEPENDENCIES END
}
So even though the plugin.xml is getting updated correctly with the before_plugin_install hook, cordova uses the old value to generate the build.gradle file.
Does anyone have any suggestions, or different routes I could take?
You don't have to write in the plugin.xml because that is only read on plugin install.
If you want to edit the gradle references you can write on the platforms/android/build.gradle file, that is where the references from the plugin.xml are copied.
Anyway, I don't think is a good idea to allow users to choose the version to use, you, as developer of the plugin, should choose the version that you tested and you are sure that works fine, using a different version might do the plugin to not work correctly or not work at all.
Took #jcesarmobile advice and instead wrote to the build.gradle file. I first check the cmd line for any arguments than I check the config.xml for any variables set for the specific plugin. Here is the script I came up with.
module.exports = function (context) {
var Q = context.requireCordovaModule('q');
var deferral = new Q.defer();
var result = null;
var externalDep = null;
var fs = require('fs'),
xml2js = require('xml2js'),
path = require('path'),
util = require('util');
var gradleLocation = process.cwd() + "/platforms/android/build.gradle";
var parser = new xml2js.Parser({explicitArray: false});
function findPluginVarInConfig() {
fs.readFile(process.cwd() + '/config.xml', function (err, data) {
parser.parseString(data, function (err, result) {
//console.log(util.inspect(result, false, null));
for (var i = 0; i < result.widget.plugin.length; i++) {
if (result.widget.plugin[i]['$'].name == 'plugin-name') {
externalDep = result.widget.plugin[i].variable['$'].value;
}
}
if (externalDep) {
console.log("found " + externalDep + " in config.xml");
replaceGradleReference();
} else {
console.log("plugin-name could not find defined dependency defined in config.xml or cmd line args defaulting to 0.0.1");
externalDep = "0.0.1";
replaceGradleReference();
}
});
});
}
function findPluginVarInCmdArgs() {
for (var i = 0; i < process.argv.length; i++) {
if (process.argv[i].indexOf('EXTERNAL_DEP') >= 0) {
externalDep = process.argv[i].replace("EXTERNAL_DEP=", "");
}
}
if (externalDep) {
console.log("found " + externalDep + " in command line args");
replaceGradleReference();
} else {
findPluginVarInConfig();
}
}
function replaceGradleReference() {
fs.readFile(gradleLocation, 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
var replaced = false;
if (data.indexOf('$INITIAL_PBR_SOURCE' >= 0)) {
result = data.replace('$INITIAL_PBR_SOURCE', function (text) {
replaced = true;
return externalDep
});
if (!replaced) {
console.log("FAILED TO ADD " + externalDep + " TO BUILD SCRIPT");
}
}
if (result) {
fs.writeFile(gradleLocation, result, 'utf8', function (err) {
if (err) {
console.log(err);
} else {
console.log("Succesfully added " + externalDep + " to buildscript");
}
deferral.resolve();
});
} else {
console.log("PBR external dependency is already added to buildscript");
}
});
}
findPluginVarInCmdArgs();
return deferral.promise;
};