Checking and removing SUSI Username Mentions in SUSI Slackbot

The SUSI Slackbot is a custom integration bot in slack. It responds to the user’s queries in the slack channels. It makes use of Slack Real Time Messaging APIs. In the background, it takes result from SUSI Server and also have some more added features. When the user mentions susi bot in the slack channels (eg. @asksusi), we know that the message is intended for susi. So now we need to remove the susi mention part, extract the rest of the message and send it to susi server. This blog explains how the messages are received from slack real time messaging API and how to remove the SUSI username mention.

Storing the bot’s self id

Before we can detect the susi username mention, we need to actually know the self id of the bot, which is a unique id assigned by the slack to the bot. Later, we use this id to detect the mention of susi.

var Slack = require('@slack/client');
var RtmClient = Slack.RtmClient;
var RTM_EVENTS = Slack.RTM_EVENTS;

var appData={};
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (connectData) => {
    // Cache the data necessary for this app in memory
    appData.selfId = connectData.self.id;
});

Receiving message and processing it

We will be receiving message from RTM API. The code for receiving the message is:

var Slack = require('@slack/client');
var RtmClient = Slack.RtmClient;
var RTM_EVENTS = Slack.RTM_EVENTS;

rtm.on(RTM_EVENTS.MESSAGE, function(message) {
var channel = message.channel;
var text=message.text;
//send reply only when mentioned or in direct message
if(text && message.user!==appData.selfId && (text.indexOf(appData.selfId)!==-1 || channel.startsWith('D'))){
    var susiMention='<@'+appData.selfId+'>';
    text=text.replace(susiMention,'');


The function passed as a callback executes only when we receive a new message. This message could be either a DM (Direct Message) or in a channel. We need to reply when the message mentions susi username or it is a DM. In the above code, we are checking if the message received is not from the bot itself, and is either from a DM or mentions susi username.
Here is a breakdown of the message object we receive in the function:

{ type: 'message',
  channel: 'C9CLRN4M7',
  user: 'U9D7JU15Y',
  text: '<@U9H78R274> hello!',
  ts: '1526413229.000321'}

 

  • Type: This tells the type of the message.
  • Channel: It contains the channel id of the slack channel where the message has been posted.
  • User: It contains the user id of the author.
  • Text: It contains the full text of the message, including mentions. We need to extract the mention part (<@U9H78R274>) and remove it.
  • Ts: ts is the unique (per-channel) timestamp.

Note: In case of DMs, the channel id will start with a “D”. In case of common channels, the channel id will start with “C”. That is how we can differentiate between a direct message and a message in a channel.

Thus we first store the bot’s self id. Then, upon receiving a message we check if the message is not from the bot itself, is a Direct Message or if it is from a channel, its mentions susi. Then only the susi bot replies to the message.

Result:

Resources

 

Continue ReadingChecking and removing SUSI Username Mentions in SUSI Slackbot

Adding System Image for Event Categories

The Open Event Server is using the JSON 1.0 Specification and build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema). In this blog, we will talk about how to add feature of System Image for Event Categories on Open Event Server. The focus is on Model updation, Schema updation and migrating the Database.

Model Updation

For adding System Image, we’ll update our Model EventTopic.

In this feature, we are providing rights to the Admin to add a system image for each Event Category so that if no image is given by a organizer of event on event creation then it will use the system image of that Event Category as event image by default.

Here we are adding a Column named system_image_url which is of type String. This value cannot be nullable and having a default value.

Migrating the Database

For the migrating the Database we will use simple commands.

This command runs migrations. If it cause problems naming Multiple Migration Head, then you need to run

This problem is caused when two developers push a migration file without merging two heads to achieve one head.

The above command will give us ids of two migration heads.

This command is merging two migration heads.

This command is upgrading the migrations.

Finally, we migrate the Database using above command.

Schema Updation

For the system image, we’ll update the Schema EventTopicSchema as follows

In this feature, to provide system image for each Event Category we’ll add a field named system_image_url in the Schema.

Here we are adding a field named system_image_url which is of marshmallow field type URL. This value cannot be none.

Validating the Event Image and using System Image by default

In this step, we’ll check if a event image is provided by organizer. If that is not provided then we’ll use system image of Event Category as Event Image.

Here, we will first take the event topic of event as added by the organizer. Then we will fetch the the database row in Event Topic model which has id == event_topic_id . Then we will return the system image url of that event topic to the event image.

So we saw how we could provide a default image for any event.

Resources

Continue ReadingAdding System Image for Event Categories

Adding option to Unfavourite image in Phimp.Me

This blog is in accordance with the P.R #1900. Here I have implemented the option to unfavourite image in the Phimp.Me android application.

Implementation

In the Phimp.Me app there are the following modes present:

1. all_photos:

All the photos are displayed in this mode irrespective of where they are saved.

2. fav_photos:

The photos which are added to favourites are displayed in the fav_photos.

3. Albums_mode:

All the albums which are present in the app are displayed in the albums_mode.

The main idea here is to find whether the selected image is already FAVOURITE or not. If it is already FAVORITED then it can be removed from that mode by removing its path form the Realm Database. If it isn’t already FAVORITED then the image is ignored and the next image is taken into consideration.

The process of removing the images from favourites can be an expensive one as the user can select myriad images which would ultimately block the Main UI. So it is better handled asynchronously and is implemented using the AsyncTask.

Whenever the user adds an image to the FAVOURITES, it gets added to the Realm Database where the model class being used is the FavouriteImagesModel.The selected media in the all_photos and fav_photos mode can be accessed via by selectedMedia.size()  and the number of selected media in the albums_mode can be accessed by getAlbum().getSelectedCount().

So in the execute method of doInBackground() a condition check is initially made and then 2 separate loops are run depending upon the mode in which the selected images exist.

Initially it is checked whether the selected image is already a FAVOURITE one or not by the following code. If it belongs to the favourite mode then the size of the favouriteImageModels would become 1.

RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, selectedMedias.get(i).getPath( )).findAll( );

If ( favouriteImagesModels.size( ) == 1) {
            favouriteImagePresent = true;
             imagesUnfavourited++;
   }

Now as the image belongs to the favourite mode we ultimately use the following code to remove the image from FAVOURITES.

 favouriteImagesModels.deleteAllFromRealm();

The full code which handle the option to unfavourite an image is shown below.

@Override
                   protected Boolean doInBackground(String arg0) {
                    

        if ( all_photos || fav_photos )   {


                           realm = Realm.getDefaultInstance();
                           realm.executeTransaction ( new Realm.Transaction( ) {
                               

                               @Override
                               public void execute (Realm realm)  {
                                   for (int i = 0 ;  i < selectedMedias.size( ) ;  i++) {
                                       RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, selectedMedias.get(i).getPath( )).findAll( );
                                       If ( favouriteImagesModels.size( ) == 1) {
                                           favouriteImagePresent = true;
                                           imagesUnfavourited++;
                                       }


                                       favouriteImagesModels.deleteAllFromRealm();
                                   }
                               }
                           });
                       }

         else if ( !fav_photos && !albumsMode ) {
                           realm = Realm.getDefaultInstance();
                           realm.executeTransaction(new Realm.Transaction() {
                           

                              @Override
                               public void execute(Realm realm) {
                                   for (int i = 0;  i < getAlbum().getSelectedCount();  i++) {
                                       RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, getAlbum( ).getSelectedMedia(i).getPath( ) ).findAll( );
                                       If ( favouriteImagesModels.size() == 1) {
                                           favouriteImagePresent = true;
                                           imagesUnfavourited++;
                                       }
                                       favouriteImagesModels.deleteAllFromRealm();
                                   }
                               }
                   }

After the doInBackground( ) method has been executed the onPostExecute( ) comes into play and some other UI related changes are done such as a SnackBar message is shown if the image is removed from favourites.

Resources

  • Realm for Android

https://realm.io/blog/realm-for-android/

  • Asynchronous Transactions in Realm

https://realm.io/docs/java/latest/#working-with-realmobjects

 

Tags: GSoC18, FOSSASIA, Phimp.Me, Unfavourite image, Realm  

Continue ReadingAdding option to Unfavourite image in Phimp.Me

Post feedback for SUSI Skills in SUSI iOS

SUSI iOS, web and Android clients allow the user to rate the SUSI Skills in a 5-star rating system. Users can write about how much particular skill is helpful for them or if improvements are needed. Users can rate skills from one to five star as well. Here we will see how to submit feedback for SUSI skills and how it is implemented on SUSI iOS.

How to submit feedback –

  1. Go to Skill Listing Screen < Skill Detail Screen
  2. Scroll to the feedback section
  3. Write feedback about SUSI skill
  4. Click on POST button to post the skill feedback

An anonymous user can not submit skill feedback. You must have to logged-in in order to post skill feedback. If you are not logged-in and click POST button to post skill feedback, an alert is presented with Login option, by clicking Login, the user is directed to Login screen where the user can log in and later can post skill feedback.

Implementation of posting skill feedback –

Google’s Material textfield is used for skill feedback text field. We have assigned TextField class from Material target to skill feedback text field to make it very interactive and give better user experience.

Skill feedback text field in the normal state –

Skill feedback text field in the active state –

When the user clicks POST after writing skill feedback, we check if the user is logged-in or not.

if let delegate = UIApplication.shared.delegate as? AppDelegate, let user = delegate.currentUser {
...
}

We have saved the logged-in user globally using AppDelegate shared method during login and using it here. The AppDelegate is sort of like the entry point for the application. It implements UIApplicationDelegate and contains methods that are called when application launches, when is going to the background (i.e. when the user hit the home key), when it’s opened back up, and more. The AppDelegate object is stored as a property on the UIApplication class and is accessible from anywhere in swift classes.

Case 1: If the user is not logged-in, we show a popup to the user with the login option

By clicking Login, the user is directed to Login screen where the user can log in and later can post skill feedback.

Case 2: If the user is already logged-in, we use the endpoint below for posting skill feedback –

http://api.susi.ai/cms/feedbackSkill.json

ModelWith following parameters –

  • Group
  • Skill
  • Feedback
  • Access token
Client.sharedInstance.postSkillFeedback(postFeedbackParam) { (feedback, success, responseMessage) in
DispatchQueue.main.async {
if success {
self.skillFeedbackTextField.text = ""
self.skillFeedbackTextField.resignFirstResponder()
self.view.makeToast(responseMessage)
} else {
self.view.makeToast(responseMessage)
}
}
}

In return response, we get feedback posted by the user –

{
feedback: "Helpful",
session:
{
...
},
accepted: true,
message: "Skill feedback updated"
}

 

Resources –

  1. Material Design Guidelines for iOS
  2. Apple’s documentation on UIApplicationDelegate API
  3. Apple’s documentation on UIApplication API
  4. ChrisRisner’s article on Singletons and AppDelegate
Continue ReadingPost feedback for SUSI Skills in SUSI iOS

Using a Git repo as a Storage & Managing skills through susi_skill_cms

In this post, I’ll be talking about SUSI’s skill management and the workflow of creating new skills

The SUSI skills are maintained in a separate github repository susi_skill_data which provides the features of version controlling and the ability to rollback to a previous version implemented in SUSI Server.

The workflow is as explained in the featured image of this blog, SUSI CMS provides the user with a GUI through which user can talk to the SUSI Server and using it’s api calls, it can manipulate the susi skills present/stored on the susi_skill_data repository.

When the user opts to create a new skill, a new createSkill component is loaded with an editor to define rules of the skill. Once the form is submitted, an AJAX POST request is made to the server which actually commits the skill data to the repository and thus it is visible in the CMS from that point on.

Grab the skill details within the editor and put them in a form which is to be sent via the POST request.

let form = new FormData();
form.append('model', 'general');
form.append('group', this.state.groupValue);
form.append('language', this.state.languageValue);
form.append('skill', this.state.expertValue.trim().replace(/\s/g,'_'));
form.append('image', this.state.file);
form.append('content', this.state.code);
form.append('image_name', this.state.imageUrl.replace('images/',''));
form.append('access_token', cookies.get('loggedIn'));


Configure POST request settings object

let settings = {
   'async': true,
   'crossDomain': true,
   'url': urls.API_URL + '/cms/createSkill.json',
   'method': 'POST',
   'processData': false,
   'contentType': false,
   'mimeType': 'multipart/form-data',
   'data': form
};


Make an AJAX request using the settings above to upload the skill to the server and send a notification when the request is successful.

$.ajax(settings)
   .done(function (response) {
   self.setState({
          loading:false
   });
notification.open({
    message: 'Accepted',
    description: 'Your Skill has been uploaded to the server',
    icon: <Icon type='check-circle' style={{ color: '#00C853' }}       />,
});


Parse the received response as JSON and if the accept key in the response is true, we push the new skill data to the history API and set relevant states.

let data = JSON.parse(response);
if(data.accepted===true){
  self.props.history.push({
	pathname: '/' + self.state.groupValue  +
  	'/' + self.state.expertValue.trim().replace(/\s/g,'_') +
  	'/' + self.state.languageValue,
	state: {
  	from_upload: true,
  	expertValue:  self.state.expertValue,
  	groupValue: self.state.groupValue ,
  	languageValue: self.state.languageValue,
}});


If the accepted key of the server response is not true, display a notification.

else{
	self.setState({
  		loading:false
	});
	notification.open({
	  	message: 'Error Processing your Request',
	  	description: String(data.message),
	  	icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
	});
}})


Handle cases when AJAX request fails and send a corresponding notification

.fail(function (jqXHR, textStatus) {
 ...
  notification.open({
    message: 'Error Processing your Request',
    description: String(textStatus),
    icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
  });
});


I hope after reading this post, the objectives of susi_skill_data are more clear and you understood how CMS handles the creation of skills.

Resources

1.AJAX Jquery – AJAX request using Jquery
2. React State – Read about React states and lifecycle hooks.

Continue ReadingUsing a Git repo as a Storage & Managing skills through susi_skill_cms

How to deploy SUSI AI bots on ngrok for debugging

For production purposes, bots can be deployed in cloud services such as HerokuGoogle App Engine or Amazon Web Services – or in their own data center infrastructure.

However, for development purposes you can use ngrok to provide access to your bot running in your local network. Ngrok is easy to setup and use. To learn more about it, you can refer to its documentation.

Ngrok is a handy tool and service that allows you tunnel requests from the wide open Internet to your local machine when it’s behind a NAT or firewall. It’s commonly used to develop web services and webhooks.

In this blog, you’ll learn how to deploy SUSI AI bots on ngrok. We’re going to demonstrate the process for SUSI AI Kik bot. However it is the same for rest of the bots as well.

First of all, you need to configure a bot on Kik. To configure a Kik bot, follow the first few steps of this blog post till you get an API key for the bot that you created using Botsworth. Now, follow these steps:

  1. In order to run SUSI AI kik bot on ngrok, you have to make some changes to the index.js file after forking susi_kikbot repository and cloning it to your local machine.
  2. Open index.js and change the information in ‘bot’ object. Write username of your kik bot in front of username and api key that you got for your bot in front of apiKey in quotations and leave the baseUrl blank for now. It will look like this: (Don’t copy the API key shown below. It’s invalid and only for demonstration purposes.)
var bot = new Bot({
    username: 'susi.ai',
    apiKey: 'b5a5238b-b764-45fe-a4c5-629fsd1851bd',
    baseUrl: ''
});
  1. Now save the file. No need to commit it.
  2. Go to https://ngrok.com/ and sign up.
  3. Next, you’ll be redirected to the setup and installation page. Follow the instructions there to setup ngrok on your local machine.
  4. Now open terminal and use ‘cd’ command to go to the susi_kikbot directory.
  5. While deploying on ngrok, we set the port for listening to http requests. Hence remove “process.env.PORT” from index.js or else this will cause an error in the next step. After removing, it should look like this: (Now save the index.js file)
http.createServer(bot.incoming()).listen(8080);

Also, comment out setInterval function in index.js.

  1. Now type the command ngrok http 8080 in terminal. In case ‘ngrok’ command doesn’t run, copy the ngrok file that you downloaded in step 5 and paste it in the susi_kikbot directory. Then enter ./ngrok http 8080 in terminal.
  2. When it launches, you’ll see a screen similar to the following:

  1. Copy the “forwarding” address (https://d36f0add.ngrok.io), and paste it in front of ‘baseUrl’ in the ‘bot’ object in index.js file. Add “/incoming” after it and save it. Now the ‘bot’ object should look similar to this:
var bot = new Bot({
    username: 'susi.ai',
    apiKey: 'b5a5238b-b764-45fe-a4c5-629fsd1851bd',
    baseUrl: 'https://d36f0add.ngrok.io/incoming'
});
  1. Launch another terminal window and cd into the susi_kikbot directory in your local machine. Then type node index.js command.

Congratulations! You’ve successfully deployed SUSI AI Kik bot on ngrok. Now try sending a message to your SUSI AI Kik bot.

References

Continue ReadingHow to deploy SUSI AI bots on ngrok for debugging

Use PreferenceManager in place of SharedPreferences

SharedPreferences is used in android to store data in the form of a key-value pair in an application. But, sometimes we need to store data at many places in the application such as saving the login email or a particular information that remains the same for the entire use of the app. In SUSI.AI android app PrefManager.java class is made that uses the sharedpreferences in android and provides a custom wrapper that is used to store the data in the key-value pair form in the app.

To store data in sharedpreferences through the PreferenceManager class we just need to declare an instance of the PreferenceManager in the location in which we want to save the data. The SUSI.AI Android app opens up the login screen and on just viewing the welcome cards for the first time due to the incorrect implementation of the sharedpreferences, using the Preference Manager we can correctly implement the preferences in welcome activity.

Whenever we install the SUSI.AI Android app, on opening it for the first time we see welcome cards that give a basic overview of the app. So, now after swiping all the cards and on reaching the final card instead of clicking GOT IT to move to the login screen and on pressing the home button we put the app in the background. Now, what happened was when the app showed cards, it was due to the WelcomeActivity.java activity.

In the onCreate() method of this activity we check whether the WelcomeActivity is opened for the first time or not. In this case we make use of the PrefManager.java class.

if (PrefManager.getBoolean(“activity_executed”, false)) {
  Intent intent = new Intent(this, LoginActivity.class);
  startActivity(intent);
  finish();
} else {
  PrefManager.putBoolean(“activity_executed”, true);
}

This piece of code calls the getBoolean(<preferenceKey>,<preferenceDefaultValue>) of the PrefManager.java class and checks the boolean value against the preference key passed in the function.

If it is the first time when this function is called then the default value is accessed against the preference key: “activity_executed”, which is passed as false and no code within the if statement is executed and so the code in the else block is executed which puts the value true as the preference value against the key “activity_executed” and the next line is executed in the sequence after this line. In short, on opening the app for the first time the preference key “activity_executed” has a value of true against it.

When we close the app by destroying it while it is in the WelcomeActivity, the onDestroy() method is called and memory is cleared, but when we open the app again we expect to see the Welcome Cards, but instead we find that we are on the login screen, even though the GOT IT button on the final card was not pressed. This is because when we go through the onCreate() method again the if condition is true this time and it sends the user directly to the Login activity.

To, solve this problem the method that was followed was :

  1. Remove the else condition in the if condition where we check the value stored against the key “activity_executed” key.
  2. In the onCreate() method of the LoginActivity.kt file use the PrefManager class to put a boolean value equal to true against the key : “activity_executed”.So the code added in the LoginActivity.kt file’s onCreate() method was :
PrefManager.putBoolean(“activity_executed”, true)

Also, the else condition was removed in the Welcome activity. Now, with these changes in place unless we click GOT IT on the final card in the welcome activity we won’t be able to go to the Login Activity, and since we won’t be able to go to the final activity the value against the key “activity_executed” will be false. Also, once we move to the login activity we will not be able to see the cards as the if condition described above will be true and the intent will fire the user to the Login Activity forever.

Therefore, using the PrefManager class it became very easy to put values or get values stored in the sharedpreferences in android. The methods are created in the Preference manager for different types corresponding to the different return types present that can be used to store the values allowed by the SharedPreferences in Android.

Using the PrefManager class functions it was made easy in SUSI.AI android app to access the SharedPreferences and using it a flaw in the auth flow and opening of the app was resolved.

 

Resources

  1. How to launch an activity only once for the first time! – Divya Jain:  https://androidwithdivya.wordpress.com/2017/02/14/how-to-launch-an-activity-only-once-for-the-first-time/
  2. Save key-value data : ttps://developer.android.com/training/data-storage/shared-preferences
  3. PrefManager Class: https://github.com/fossasia/susi_android/blob/development/app/src/main/java/org/fossasia/susi/ai/helper/PrefManager.java
Continue ReadingUse PreferenceManager in place of SharedPreferences

Chrome Custom Tabs Integration – SUSI.AI Android App

Earlier, we have seen the apps having external links that opens and navigates the user to the phone browser when clicked, then we came up with something called WebView for Android, but nowadays we have shifted to something called In-App browsers. The main drawback of the system/ phone browsers are they caused heavy transition. To overcome this drawback “Chrome Custom Tabs” were invented which allowed users to walk through the web content seamlessly.

SUSI.AI Android App earlier used the system browser to open any link present in the app.

This can be implemented easily by

Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent);

This lead to a huge transition between the context of the app and the web browser.

Then, to reduce all the clutter Chrome Custom tabs by Google was evolved which drastically increased the loading speed and the heavy context switch was also not taking place due to the integration and adaptability of custom tabs within the app.

Chrome custom tabs also are very secured like Chrome Browser and uses the same feature and give developers a more control on the custom actions, user interface within the app.

                                comparing the load time of the above mentioned techniques

Ref : Android Dev – Chrome Custom Tabs

Integration of Chrome Custom Tabs

  • Adding the dependency in build.gradle(app-level) in the project
dependencies {
    //Other dependencies 
    compile 'com.android.support:customtabs:23.4.0'
}
  • Now instantiating a CustomTabsIntent Builder

    String url = “https://www.fossasia.org” // can be any link
    
    CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); //custom tabs intent builder
    
    CustomTabsIntent customTabsIntent = builder.build();
  • We can also add animation or customize the color of the toolbar or add action buttons.

    builder.setColor(Color.RED) //for setting the color of the toolbar 
    builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left); //for start animation
    builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right); //for exit animation
  • Finally, we have have achieved everything with a little code. Final launch the web page

    Uri webpage = Uri.parse(url); //We have to pass an URI
    
    customTabsIntent.launchUrl(context, webpage); //launching through custom tabs

Benefits of Chrome Custom Tabs

  1. UI Customization are easily available and can be implemented with very few lines of code. 
  2. Faster page loading and in-app access of the external link 
  3. Animations for start/exit  
  4. Has security and uses the same permission model as in chrome browser.

Resources

  1. Chrome Custom Tabs:  https://developer.chrome.com/multidevice/android/customtabs
  2. Chrome Custom Tabs Github Repo: GitHub – GoogleChrome/custom-tabs-client: Chrome custom tabs
  3. Android Blog: Android Developers Blog: Chrome custom tabs smooth the transition
  4. Video: Chrome Custom Tabs: Displaying 3rd party content in your Android

 

Continue ReadingChrome Custom Tabs Integration – SUSI.AI Android App

Building PSLab Android app with Fdroid

Fdroid is a place for open source enthusiasts and developers to host their Free and Open Source Software (FOSS) for free and get more people onboard into their community. Hosting an app in Fdroid is not a fairly easy process just like hosting one in Google Play. We need to perform a set of build checks prior to making a merge request (which is similar to pull request in GitHub) in the fdroid-data GitLab repository. PSLab Android app by FOSSASIA has undergone through all these checks and tests and now ready to be published.

Setting up the fdroid-server and fdroid-data repositories is one thing. Building our app using the tools provided by fdroid is another thing. It will involve quite a few steps to get started. Fdroid requires all the apps need to be built using:

$ fdroid build -v -l org.fossasia.pslab

 

This will output a set of logs which tell us what went wrong in the builds. The usual one in a first time app is obviously the build is not taking place at all. The reason is our metadata file needs to be changed to initiate a build.

Build:<versioncode>,<versionname>
    commit=<commit which has the build mentioned in versioncode>
    subdir=app
    gradle=yes

 

When a metadata file is initially created, this build is disabled by default and commit is set to “?”. We need to fill in those blanks. Once completed, it will look like the snippet above. There can be many blocks of “Build” can be added to the end of metadata file as we are advancing and upgrading through the app. As an example, the latest PSLab Android app has the following metadata “Build” block:

Build:1.1.5,7
    commit=0a50834ccf9264615d275a26feaf555db42eb4eb
    subdir=app
    gradle=yes

 

In case of an update, add another “Build” block and mention the version you want to appear on the Fdroid repository as follows:

Auto Update Mode:Version v%v
Update Check Mode:Tags
Current Version:1.1.5
Current Version Code:7

 

Once it is all filled, run the build command once again. If you have properly set the environment in your local PC, build will end successfully assuming there were no Java or any other language syntax errors.

It is worth to mention few other facts which are common to Android software projects. Usually the source code is packed in a folder named “app” inside the repository and this is the common scenario if Android Studio builds up the project from scratch. If this “app” folder is one level below the root, that is “android/app”, the build instructions shown above will throw an error as it cannot find the project files.

The reason behind this is we have mentioned “subdir=app” in the metadata file. Change this to “subdir=android/app” and run the build again. The idea is to direct the build to find where the project files are.

Apart from that, the commit can be represented by a tag instead of a long commit hash. As an example, if we had merge commits in PSLab labeled as “v.<versioncode>”, we can simply use “commit=v.1.1.5” instead of the hash code. It is just a matter of readability.

Happy Coding!

Reference:

  1. Metadata : https://f-droid.org/docs/Build_Metadata_Reference/#Build
  2. PSLab Android app Fdroid : https://gitlab.com/fdroid/fdroiddata/merge_requests/3271/diffs
Continue ReadingBuilding PSLab Android app with Fdroid

Using file.io for Meilix Deployment

Meilix is developed in FOSSASIA and is deployed as a release on Github, but the download speed on GitHub for large files is slow. Alternatively deployment can be done on 3rd party servers. transfer.sh offers a good service for a start, but they only have a reduced storage for heavy usage such as what is required for Meilix. file.io is a good alternative. file.io has API features which can be used in .travis.yml to deploy meilix. For the time being, we are deploying a generator branch to file.io

Changes made in the .travis.yml

We need to edit the .travis.yml to deploy the artifact on file.io

deploy:
provider: script
script: curl -F "file=@/home/travis/$(image_name)" https://file.io
on:
branch: generator

We need to edit the deploy attribute in the travis to get the deployment done in file.io. Query contains the address of the file which needs to be uploaded on file.io. Then the branch name is provided on which deployment needs to be done.

Output:

Travis executes the command to deploy the application.

{"success":true,"key":"5QBEry","link":"https://file.io/5QBEry","expiry":"14 days"}

success: true  the artifact is successfully deployed.

key and link: gives the link from where the ISO can be downloaded.

expiry: tell the number of days after which the ISO will be deleted and by default it is set to 14 days.

We can manually input expiry parameter to declare the expiry time.

script: curl -F "file=@/home/travis/$(image_name)" https://file.io/?expires=1w

This will set the expiry time to seven days. If you set it with w, it will be number of weeks, m will be for number of months, y will be number of years.

file.io solved the most important issue of meilix deployment and this approach can be use several different project of FOSSASIA for the deployment purpose.

References:

Continue ReadingUsing file.io for Meilix Deployment