OpenSource - Flashcards system w/ Tutorial - Online Courses, Schools, and Other External Resour

About this project:
This project has been created in hopes for finally making language learning 100% free. LingoIO has already created software for native-level translations for various languages, and is slowly releasing source code to broaden the audience, and get developers into creating plugins for the software. Afterall, everyone should have free education. (This project supports both Android and iOS)
With this tutorial, you will learn VARIOUS elements to building the software from the ground up. This includes, but is not limited to very wide range of functionalities as listed below​
AngularJS
Loading data from JSON (soon.....)
Parsing JSON data (soon.....)
Custom Animation / Functionality (soon.....)
Building a flash-cards system
Android AppGyver
Preparing a template
Building the backbone
PHP - Where most of the magic happens (soon.....)
Working with the database
Spanish Syntax Mapping
Spanish word-ordering detection
Handling translation responses
Progress system
For now, we will go piece-by-piece .
[Getting your project setup]:
The good thing is that we are building this from scratch, so, the only requirement is that you have Steroids JS installed, which you can grab at AppGyver . They have an EXTREMELY easy tutorial on getting setup. It's much better than we could ever make in a post, so, please follow their install wizard ^_^​Now that we have that out the way, let's take a look at the AngularJS controllers, and how they are setup.​
The structure of an Index Card
Code:
| card_stack
--| card
----| title
----| cardID
----| answers
-------| answer
-------| description
card_stack - This is basically your 'deck' of cards; more or less.
ard - This is the actual index card
title - This is the cards title, or in other words, the english word that needs to be translated.
cardID - We need to assign this value so that we can do DOM manipulation on a specific card.
answers - This is an array of answers. In most languages, there is more than one way to say something, so, we support alternative answers, if you will .
answer - This contains the translation of the card to the target language
description - A description of the index card
Click to expand...
Click to collapse
Setting up our controllers Backbone
In your application path, we should first find the file 'app\example\scripts\LearnMoreController'. This is the file we will be using as our main controller.​
We should start by creating a reference to our controller, which is quite easy:
Code:
//example is the name of the default module created.
var LIMP = angular.module("example");
//Alternatively, you may opt to stick with the default reference which would be the following
angular.module("example").controller
angular.module("example").factory
//So on and so forth . . . .
Now that we can interact with our application, let's go ahead and setup our initial Learning controller
Code:
//Remember the Limp variable we set? It should go right above this function
var LIMP = angular.module("example");
//Notice that we have made references to $scope, $element, and supersonic
//These are required
LIMP.controller('FlashcardController', function ($scope, $element, supersonic) {
});
We can now interact with our controller ^_^ . But we still have a ways to go. . . .
We can now setup our initial deck, and let our scope know what it contains. Add the following inside the controller
Code:
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
We now have three flashcards: "hello", "the dog", and "the cat" with their respective answers.
Now, we want to be able to test on flash-cards don't we? For that, there are a few things we have to do.
We need to create another controller that will handle our study system. We will call this 'StudyController'
It should be placed under the FlashcardController.
Code:
LIMP.controller("StudyController", function ($scope, $element, supersonic) {
});
Again, define your card_stack -- but this time, let's make another reference
Code:
LIMP.controller("StudyController", function ($scope, $element, supersonic) {
//STACK OF CARDS
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
$scope.study_cards = $scope.card_stack;
});
We have our cards defined that we will test on. Now, there are a few variables you need to define, so that we can keep track of question progress
Code:
LIMP.controller("StudyController", function ($scope, $element, supersonic) {
//STACK OF CARDS
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
$scope.study_cards = $scope.card_stack;
//STORES VALUE FOR THE CURRENT QUESTION
$scope.currentQuestion = "";
//KEEP TRACK OF PROGRESS ( WHICH QUESTION ARE WE AT, HOW MANY DO WE HAVE )
$scope.studyIndex = 0;
$scope.studyCount = $scope.study_cards.length;
//NUMBER OF ANSWERS CORRECT / FALSE
$scope.numCorrect = 0;
$scope.numWrong = 0;
//SET FIRST QUESTION TITLE
$scope.currentQuestion = $scope.card_stack[0].title
});
We are almost done with a basic flash-cards system, but we have two functions that we need to setup.
$scope.submitAnswer() and $scope.studySessionComplete()
The two functions should be added inside the StudyController, under the $scope.currentQuestion like so
Code:
LIMP.controller("StudyController", function ($scope, $element, supersonic) {
//STACK OF CARDS
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
$scope.study_cards = $scope.card_stack;
//STORES VALUE FOR THE CURRENT QUESTION
$scope.currentQuestion = "";
//KEEP TRACK OF PROGRESS ( WHICH QUESTION ARE WE AT, HOW MANY DO WE HAVE )
$scope.studyIndex = 0;
$scope.studyCount = $scope.study_cards.length;
//NUMBER OF ANSWERS CORRECT / FALSE
$scope.numCorrect = 0;
$scope.numWrong = 0;
//SET FIRST QUESTION TITLE
$scope.currentQuestion = $scope.card_stack[0].title
//Submit the answer
$scope.submitAnswer = function () {
//alert("submit answer");
var answer = $("#submission_text").val();
//SET ANSWERS IN VARIABLE the_answers
var the_answers = $scope.study_cards[$scope.studyIndex].answers;
//VARIABLE FOR ANSWER RESPONSE
var answer_correct = false;
//CHECK IF ANSWER CORRECT
//LOOP THROUGH EACH ANSWER
for (var i = 0 ; i < the_answers.length; i++) {
if ($scope.study_cards[$scope.studyIndex].answers[i].answer == answer) {
answer_correct = true;
}
}
if (answer_correct) { //IF ANSWER IS CORRECT
$scope.numCorrect++; // +1
} else { //IF ANSWER IS WRONG
$scope.numWrong++; // +1
}
//NEW QUESTION
$scope.studyIndex ++;
if ($scope.studyIndex >= $scope.studyCount) {
$scope.studySessionComplete();
} else {
//SET NEW QUESTION TITLE
$scope.currentQuestion = $scope.study_cards[$scope.studyIndex].title;
}
$("#submission_text").val("");
};
//Study session is finished
$scope.studySessionComplete = function () {
$("#gameBoard").fadeOut(1500, function () {
$("#scoreBoard").fadeIn(function () {
alert("All done here....");
});
});
}
}
});
$scope.studySessionComplete tells our UI we are finished, and to display stats.
This simply hides the gameBoard, and displays the scoreBoard as defined in our HTML
$scope.submitAnswer handles our submission of answers. Here is how it works
We get value of the input box, and set it as the answer
Then, we get a list of possible answers for the question we are currently on.
The app then loops through those answers, and looks for a match.
Next, if a match is found, add points -- and vice versus for wrong
Finally, we check to see if we are done testing. If not, increase the index -- and set the next question. If we are, $scope.studySessionComplete() is called.
We are done with the JS portion. Your code should look like this:
Code:
LIMP.controller('FlashcardController', function ($scope, $element, supersonic) {
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
$scope.startTest = function () {
//alert("SAFDA");
var view = new supersonic.ui.View("example#learnNow");
supersonic.ui.layers.push(view);
}
});
LIMP.controller("StudyController", function ($scope, $element, supersonic) {
//STACK OF CARDS
$scope.card_stack = [{ "title": "Hello", "cardID": "Hello", "answers": [{ "answer": "Hola", "description": "Saying hi" }] }, { "title": "The Dog", "cardID": "the_dog", "answers": [{ "answer": "El perro", "description": "(masc) Animal" }, { "answer": "La perra", "description": "(fem) Animal" }] }, { "title": "The cat", "cardID": "the_cat", "answers": [{ "answer": "El gato", "description": "(masc) Animal" }, { "answer": "La gata", "description": "(fem) Animal" }] }];
$scope.study_cards = $scope.card_stack;
//STORES VALUE FOR THE CURRENT QUESTION
$scope.currentQuestion = "";
//KEEP TRACK OF PROGRESS ( WHICH QUESTION ARE WE AT, HOW MANY DO WE HAVE )
$scope.studyIndex = 0;
$scope.studyCount = $scope.study_cards.length;
//NUMBER OF ANSWERS CORRECT / FALSE
$scope.numCorrect = 0;
$scope.numWrong = 0;
//SET FIRST QUESTION TITLE
$scope.currentQuestion = $scope.card_stack[0].title
//Submit the answer
$scope.submitAnswer = function () {
//alert("submit answer");
var answer = $("#submission_text").val();
//SET ANSWERS IN VARIABLE the_answers
var the_answers = $scope.study_cards[$scope.studyIndex].answers;
//VARIABLE FOR ANSWER RESPONSE
var answer_correct = false;
//CHECK IF ANSWER CORRECT
//LOOP THROUGH EACH ANSWER
for (var i = 0 ; i < the_answers.length; i++) {
if ($scope.study_cards[$scope.studyIndex].answers[i].answer == answer) {
answer_correct = true;
}
}
if (answer_correct) { //IF ANSWER IS CORRECT
$scope.numCorrect++; // +1
} else { //IF ANSWER IS WRONG
$scope.numWrong++; // +1
}
//NEW QUESTION
$scope.studyIndex ++;
if ($scope.studyIndex >= $scope.studyCount) {
$scope.studySessionComplete();
} else {
//SET NEW QUESTION TITLE
$scope.currentQuestion = $scope.study_cards[$scope.studyIndex].title;
}
$("#submission_text").val("");
};
//Study session is finished
$scope.studySessionComplete = function () {
$("#gameBoard").fadeOut(1500, function () {
$("#scoreBoard").fadeIn(function () {
alert("All done here....");
});
});
}
});
Last but not least, let's tie our code to our JS ^_^
For starters, you need to open the structure file found at
{project_name}/config/structure.coffee
It should be this:
Code:
module.exports =
# See styling options for tabs and other native components in app/common/native-styles/ios.css or app/common/native-styles/android.css
tabs: [
{
title: "Vocabulary"
id: "index"
location: "example#flashcards" # URLs are supported!
}
{
title: "LingoIO"
id: "internet"
location: "lingoio.com" # URLs are supported!
}
{
title: "Twitter"
id: "internet"
location: "twitter.com/lingo_io" # URLs are supported!
}
]
# rootView:
# location: "example#getting-started"
preloads: [
{
id: "learn-now"
location: "example#learnNow"
}
{
id: "using-the-scanner"
location: "example#using-the-scanner"
}
]
# drawers:
# left:
# id: "leftDrawer"
# location: "example#drawer"
# showOnAppLoad: false
# options:
# animation: "swingingDoor"
#
# initialView:
# id: "initialView"
# location: "example#initial-view"
Finally, add the following files to folder {project_name}/app/example/views
learnNow.html
Code:
<link href="/Bootstrap3/css/bootstrap.css" rel="stylesheet" />
<link href="/Bootstrap3/css/bootstrap-theme.css" rel="stylesheet" />
<div ng-controller="StudyController">
<div id="gameBoard" class="list">
<div class="item">
Question: {{numCorrect}} / {{studyIndex}}
</div>
<div class="item">
Number Correct: {{numCorrect}} / {{studyIndex}}
</div><br /><br />
<div class="item">
How do you say <strong>'<big>{{currentQuestion}}</big>'</strong> in English ?
</div>
<div class="item item-divider"></div>
<div class="item">
<input id="submission_text" type="text" style="padding: 15px; width: 100%;" placeholder="Answer..." /><br /><br />
<input type="button" class="btn btn-primary" style="padding: 15px; width: 100%;" Value="Submit Answer" ng-click="submitAnswer()" />
</div>
</div>
<div id="scoreBoard" style="display: none;">
<h1>Congratulations on completing your study session ! ^_^</h1>
You have studied the following<br />
<div class="item" ng-repeat="card in card_stack">
<div id="{{card.cardID}}">
<div class="item item-divider">
<span style="display: inline-block">{{card.title}}</span><span style="display: inline-block; float: right"><img src="/star.png" height="25" /></span>
</div>
</div>
</div>
</div>
</div>
flashcards.html
Code:
<link href="/Bootstrap3/css/bootstrap.css" rel="stylesheet" />
<link href="/Bootstrap3/css/bootstrap-theme.css" rel="stylesheet" />
<div class="padding" ng-controller="FlashcardController">
<div class="list">
<div class="item" ng-repeat="card in card_stack">
<div id="{{card.cardID}}">
<div class="item item-divider">
<span style="display: inline-block">{{card.title}}</span><span style="display: inline-block; float: right"><img src="/star.png" height="25" /></span>
</div>
<div ng-repeat="answer in card.answers" class="padding item item-divider">
<code>{{answer.answer}}</code><br /><br />
<p style="color: darkgreen; padding: 15px;">
<code>{{answer.description}}</code>
</p>
</div>
</div>
</div>
</div>
<div ng-click="startTest();" class="btn btn-primary" style="margin: auto; padding: 15px; width: 200px;">Hello</div>
</div>
As I find time, I will add other elements of this tutorial such as the back-end, syntax/word mapping, etc! Be patient ^_^
Check out the full source-code here on Github: github.com/montraydavis/AppGyver-Flashcards/
twitter.com/lingo_io

RESERVED......

Reserved post for future tutorials

Last reserved post for future tutorials.

Related

Quick App Generation for Test by Quick App IDE

This is originally from HUAWEI Developer Forum
Forum link: https://forums.developer.huawei.com/forumPortal/en/home
​
Do you want to test a web page whether is functional or not before providing solutions to CPs you can use Quick App IDE for testing just in a few steps:
1) Create a new project by using File > New Project > New QuickApp Project... direction. Enter App Name, Package Name and select "Hello World" template.
2) Open "hello.ux" file folder and paste following script. You only need to change loadUrl adress that you want to test e.g. https://developer.huawei.com/consumer/en/doc/
Code:
template>
<div class="doc-page">
<!-- Replace the link to the HTML5 app -->
<web class="web-page" src="{{loadUrl}}" trustedurl="{{list}}" onpagestart="onPageStart" onpagefinish="onPageFinish"
onmessage="onMessage" ontitlereceive="onTitleReceive"
onerror="onError" id="web"
supportzoom="{{supportZoom}}"
wideviewport="{{wideViewport}}}"
overviewmodeinload="{{overViewModeLoad}}"
useragent="{{ua}}"
allowthirdpartycookies="{{allowThirdPartyCookies}}">
</web>
</div>
</template>
<style>
.doc-page {
flex-direction: column;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
width: 100%;
height: 100%;
position: fixed;
}
.web-page {
width: 100%;
height: 100%;
}
</style>
<script>
import router from "@system.router";
export default {
props: ['websrc'],
data: {
title: "",
// TODO Replace the link to the H5 app
loadUrl: "https://developer.huawei.com/consumer/en/doc/",
// Attribute allowthirdpartycookies, indicates whether cookies can be delivered in cross-domain mode.
// If you need login Google Account or Other Account, Please set TRUE.
allowThirdPartyCookies: true,
//Attribute supportzoom, indicates whether the H5 page can be zoomed with gestures.
supportZoom:true,
wideViewport:true,
overViewModeLoad:true,
ua:"default",
// Here the whitelist settings, when the loading page has multiple addresses, such as the successful loading of the login address and the inconsistent entry address, it needs to set the whitelist to do so.
list: ["new RegExp('https?.*')"]
},
onInit() {
console.info('onInit: ');
},
onPageStart(e) {
console.info('pagestart: ' + e.url)
},
// Each page switch triggers
onPageFinish(e) {
console.info('pagefinish: ' + e.url, e.canBack, e.canForward);
},
onTitleReceive(e) {
this.title = e.title;
},
onError(e) {
console.info('pageError : ' + e.errorMsg)
},
onMessage(e) {
console.info('onmessage e = ' + e.message + ", url = " + e.url);
},
onShow: function () {
console.info(" onshow");
},
onHide: function () {
console.info(" onHide");
},
onBackPress() {
console.log('onBackPress')
this.$element('web').canBack({
callback: function (e) {
if (e) {
console.log('web back')
this.$element('web').back()
} else {
console.log('router back')
router.back()
}
}.bind(this)
})
return true
},
}
</script>
3) Put your app icon under Common folder. E.g. developer.png
4) Open manifest.json file and paste follwing script and change icon name with the file that you uploaded under Common folder.
Code:
{
"package": "com.testapp.huwei",
"name": "TestApp",
"versionName": "1.0.0",
"versionCode": 1,
"icon": "/Common/developer.png",
"minPlatformVersion": 1060,
"features": [
{
"name": "system.configuration"
},
{
"name": "system.calendar"
},
{
"name": "system.prompt"
},
{
"name": "system.router"
}
],
"permissions": [
{
"origin": "*"
}
],
"config": {},
"router": {
"entry": "Hello",
"pages": {
"Hello": {
"component": "hello"
}
}
},
"display": {
"titleBar":false
}
}
4) Create .rpk package by using Build > Run Release direction.
5) Accept IDE to create a signature file for your app.
6) com.testapp.huawei_release_1.0.0.rpk package has been generated and ready for test.

Integrating Location kit in Quick App

More information like this, you can visit HUAWEI Developer Forum​
I have written series of article on Quick App. If you are new to Quick App refer my previous articles.
Quick App set up
A Novice Journey Towards Quick App ( Part 2 )
A Novice Journey Towards Quick App ( Part 3 )
In this article we will learn how to use Location Kit in Quick App.
Introduction
Geolocation is the ability to track a device’s using GPS, cell phone towers, Wi-Fi access points or a combination of these. Huawei Location Kit combines the GPS, Wi-Fi, and base station locations to help you quickly obtain precise user locations.
Should have
1. To integrate the location kit in Quick App you should have developer account. Register here.
2. Android version 4.4 or later.
3. HMS core version 3.0.0.300 later has to be installed.
4. Enable HMS Core Location permission, Setting >Apps > HMS Core > Permission.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
5. Enable location permission for application to be tested.
6. Location service should be enabled in the notification panel.
7. Test application in good network signal to get the more accurate location information.
Steps to be followed.
1. Create project (refer Quick App Part 1).
2. Add the feature attribute in manifest.json.
Code:
{
"name": "system.geolocation"
}
3. Import the geolocation in the script
Code:
import geolocation from '@system.geolocation'
4. Design Location kit screen.
1) geolocation.getLocation(cordType, timeout, success, fail, complete)
· cordType: wgs84: This is the default value. It indicates that the geographical location will be returned by invoking the system location ability.
· Timeout: timeout duration in milliseconds. Default value is 30000. It is mandatory
· Success: It is callback function used when interface is successfully called.
· Fail: Callback function used when the interface fails to be called.
· Complete: Callback function used when the interface call is completed.
2) geolocation.subscribe(cordType, callback, fail)
· cordType: wgs84: This is the default value. It indicates that the geographical location will be returned by invoking the system location ability.
· Callback: It is called whenever location information changed.
· Fail: Callback function used when the interface fails to be called.
3) gelocation.unsubscribe() This is cancel the monitoring the geographical location.
4) geolocation.getLocationType(success, fail, complete): To get the location types currently supported by the system.
5) geolocation.openLocation: This is to view the location on the built in map. Currently it is Supported only in Chinese mainland.
6) geolocation.chooseLocation: This it open map and select location. Currently it is supported only in Chinese mainland.
Code:
<template>
<div class="container">
<div class="page-title-wrap">
<text class="page-title">{{componentName}}</text>
</div>
<div class="item-content">
<text class="txt">{{$t('Current Location')}}</text>
<text class="txt">latitude: {{geolocationGetData.latitude}}</text>
<text class="txt">longitude: {{geolocationGetData.longitude}}</text>
<text class="txt">altitude: {{geolocationGetData.altitude}}</text>
<text class="txt">accuracy: {{geolocationGetData.accuracy}}</text>
<text class="txt">heading: {{geolocationGetData.heading}}</text>
<text class="txt">speed: {{geolocationGetData.speed}}</text>
<text class="txt">time: {{geolocationGetData.time}}</text>
</div>
<input type="button" class="btn" onclick="getGeolocation" value="{{$t('Get My Current Location')}}" />
<div class="item-content">
<text class="txt">{{$t('Location')}}</text>
<text class="txt">latitude: {{geolocationListenData.latitude}}</text>
<text class="txt">longitude: {{geolocationListenData.longitude}}</text>
<text class="txt">accuracy: {{geolocationListenData.accuracy}}</text>
<text class="txt">time: {{geolocationListenData.time}}</text>
</div>
<input type="button" class="btn" onclick="listenGeolocation" value="{{$t('Monitor Location')}}" />
<input type="button" class="btn" onclick="cancelGeolocation" value="{{$t('Cancel Monitoring Location')}}" />
<div class="item-content">
<text class="txt">{{$t('Location Type: ')}}{{typeVaule}}</text>
</div>
<input type="button" class="btn" onclick="getLocationType" value="{{$t('Location type')}}" />
<input type="button" class="btn" onclick="openLocation" value="Open Location in Map" />
<input type="button" class="btn" onclick="chooseLocation" value="Choose location from Map" />
</div>
</div>
</template>
<style>
@import "../../../common/css/common.css";
.item-container {
margin-bottom: 30px;
margin-right: 60px;
margin-left: 60px;
flex-direction: column;
color: #ffffff;
}
.item-content {
flex-direction: column;
padding: 30px;
margin-bottom: 50px;
align-items: flex-start;
color: #ffffff;
}
.txt{
color: #ffffff;
}
</style>
<script>
import geolocation from '@system.geolocation'
import prompt from '@system.prompt'
export default {
data: {
componentName: 'Location Kit',
componentData: {},
deviceInfo: '',
isHuawei: false,
time: '',
geolocationGetData: {
latitude: '',
longitude: '',
altitude: '',
accuracy: '',
heading: '',
speed: '',
time: ''
},
geolocationListenData: {
latitude: '',
longitude: '',
time: '',
accuracy: ''
},
typeVaule: ''
},
onInit: function () {
this.$page.setTitleBar({ text: 'Location Kit' })
this.componentData = this.$t('message.interface.system.geolocation');
},
getGeolocation: function () {
var that = this;
if (that.isHuawei) {
prompt.showToast({
message: this.componentData.baiduMap
})
geolocation.getLocation({
coordType: "gcj02",
timeout: 2000,
success: function (ret) {
that.geolocationGetData = ret;
console.log(that.geolocationGetData.time);
var date = new Date(that.geolocationGetData.time);
that.geolocationGetData.time = date;
},
fail: function (erromsg, errocode) {
console.log('geolocation.getLocation----------' + errocode + ': ' + erromsg)
},
complete: function () {
console.log('geolocation complete----------')
}
})
} else {
prompt.showToast({
message: this.componentData.systemMap
})
geolocation.getLocation({
timeout: 2000,
success: function (ret) {
that.geolocationGetData = ret;
var time = new Date().getTime();
console.log(that.geolocationGetData.time);
var date = new Date(that.geolocationGetData.time);
that.geolocationGetData.time = date;
},
fail: function (erromsg, errocode) {
console.log('geolocation.getLocation----------' + errocode + ': ' + erromsg)
},
complete: function () {
console.log('geolocation complete----------')
}
})
}
},
listenGeolocation: function () {
var that = this;
geolocation.subscribe({
callback: function (ret) {
that.geolocationListenData = ret;
console.log(that.geolocationListenData.time);
var date = new Date(ret.time);
that.geolocationListenData.time = date;
},
fail: function (erromsg, errocode) {
console.log('geolocation.subscribe----------' + errocode + ': ' + erromsg)
}
})
},
cancelGeolocation: function () {
geolocation.unsubscribe();
},
getLocationType: function () {
var that = this;
geolocation.getLocationType({
success: function (data) {
that.typeVaule = data.types;
console.log("ret - " + data.types)
}
})
},
openLocation: function(){
geolocation.openLocation({
latitude: 12.972442,
longitude: 77.580643,
coordType:"gcj02",
name: "Bangalore",
address: "Bangalore",
scale: 18,
success: function () {
console.log('openLocation success .');
},
fail: function (erromsg, errocode) {
console.log('geolocation.openLocation----------' + errocode + ': ' + erromsg)
},
complete: function () {
console.log('openLocation complete.');
}
})
},
chooseLocation: function(){
console.log("chooseLocation");
geolocation.chooseLocation({
latitude: 12.972442,
longitude: 77.580643,
coordType:"gcj02",
success: function (data) {
console.log('chooseLocation success �� ' + JSON.stringify(data));
},
fail: function (error) {
console.log('chooseLocation fail : ' + error.message);
},
complete: function () {
console.log('chooseLocation complete.');
}
})
}
}
</script>
Result
Conclusion
In this article, we have learnt how to integrate the Location kit in Quick App. In upcoming article I will come up with new concept.
Reference
Location kit official document

Expert: Develop weather application for HarmonyOS consuming REST APIs

Introduction
In this article, I have explained to develop weather application for HarmonyOS using Huawei DevEco Studio and using HTML, JavaScript and Open Rest APIs. User can search the city name and fetch the information. Application will show current weather and weather prediction for next five days. The UI is developed with flexible rich HTML with JavaScript. Network calls are done using Java HttpClient.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Huawei Mobile Device
Requirements
1) DevEco IDE
2) Huawei phone running Harmony OS (Can use cloud emulator also)
New Project (Phone)
After installation of DevEco Studio, make new project.
Select Phone in Device and select Empty Feature Ability (JS) in Template.
After the project is created, its directory as shown in image.
hml files describe the page layout.
css files describe the page style.
js files process the interactions between pages and users.
The app.js file manages global JavaScript logics and application lifecycle.
The pages directory stores all component pages.
The java directory stores java files related to the projects.
Development process
Design the UI
We are designing a simple UI, with just single page which will display the current weather and predicted weather for next five days. We need three UI section in this page.
Search box for searching the city name
UI Section showing today’s weather
UI Section with carousel to display next five days
Step 1: Create the search box in the hml file.
As the first step, we can create an input component that will be holding the search area which will be used for searching the city.
index.html
Code:
<div class="container">
<div>
<div class="title">
<input class="comment" value="{{searchValue}}" placeholder ="Enter the city " onchange="updateSearchValue()"></input>
</div>
<image class="searchart" src="/common/search1.png" onclick="searchCity() "></image>
</div>
</div>
index.css
Code:
.container {
flex-direction: column;
background-color: #e8f6fe;
}.comment {
width: 550px;
height: 100px;
background-color: lightgrey;
}.searchart {
margin-top:40px;
width:70px;
height:68px;
margin-right:40px;
margin-left:-40px;
}
index.js
Code:
updateSearchValue(e) {
this.searchValue = e.text;
},searchCity() {
this.inProgress = true;
this.fetchWeather(this.searchValue).then()
},
Result
​
Step 2: Add UI section to display today’s weather.
Create Text fields for city name, todays date, temperature, precipitation and wind speed. Then we have section for description of today’s weather in few words like sunny, clear sky and so on. Finally we have image which depicts what type of weather it is.
index.hml
Code:
<div class="widget">
<div class="details">
<text class="city">{{currentCityInfo}}</text>
<text class="today">Today {{toDay}}</text>
<text class="temperature">{{currentTemperature}}</text>
<text class="precipitation">Precipitation: {{currentHumidity}}</text>
<text class="wind">Wind: {{currentWind}} km/hr</text>
<div>
<div class="summary">
<text class="summaryText">{{currentDesc}}</text>
</div>
<image class="weatherart" src="{{artImage}}"></image>
</div>
</div>
</div>
index.css
Code:
.temperature {
color: white;
font-weight: 300;
font-size: 150px;
}
.today {
color: white;
font-weight: 300;
font-size: 32px;
margin-top: 20px;
width: 420px;
padding-top: 10px;
border-top: 2px solid #9cd0ff;
}
.city {
color: white;
font-size: 70px;
margin-top: 0px;
}
.summary {
width: 660px;
margin-top: 16px;
padding-bottom: 16px;
border-top: 2px solid #9cd0ff;
}
.summaryText {
color: #d2e9fa;
font-size: 70px;
font-weight: 300;
margin: 0;
margin-left: 40px;
margin-top: 40px;
}
.precipitation, .wind {
color: #d2e9fa;
font-size: 32px;
font-weight: 300;
margin-left: 8px;
}
.precipitation {
margin-top: 16px;
}
Result
Step 3: Add UI section for next five days weather.
Now we have search box and today weather UI section. Below those add a carousel UI using swiper component. Each item in the swiper will have text fields for max and min temperature and an icon for weather indication.
index.html
Code:
<div class="daystitle"><text class="name">Next 4 days</text></div><swiper id="swiperImage" class="swiper-style">
<div class="daydetailscard" for="{{day in days}}">
<text class="daydetitle">{{day.dayName}}</text>
<div class="daydetailssubcard">
<text class="detailstemp">Hi : {{day.maxTemp}}°C</text>
<text class=" detailstemp ">Low : {{day.minTemp}}°C</text>
<image class="weatherarticon" src="{{day.artImageIcon}}"></image>
</div>
<text class="daydetails" >{{day.desc}}</text>
</div>
</swiper>
index.css
Code:
.daydetitle{
color: #626262;
text-align: center;
font-size: 40px;
padding-bottom: 10px;
border-bottom: 2px solid #626262;
font-family: Roboto, sans-serif;
display: flex;
flex-direction: column;
margin-top: 40px;
margin-bottom: 40px;
}
.daydetails{
color: white;
text-align: center;
font-size: 40px;
font-family: Roboto, sans-serif;
display: flex;
flex-direction: column;
margin-top: 40px;
margin-bottom: 40px;
}
.daydetailscard{
border-radius: 28px;
height: 300px;
width: 630px;
background: linear-gradient(to bottom right, #ffb20f 20%, #ecdebc);
font-family: Roboto, sans-serif;
display: flex;
flex-direction: column;
margin-top: 10px;
margin-left: 40px;
}
.daydetailssubcard{
height: 50px;
width: 630px;
font-family: Roboto, sans-serif;
display: flex;
flex-direction: row;
}
.deatilstemp {
color: white;
font-size: 32px;
font-weight: 300;
margin-left: 20px;
margin-top: 16px;
}
Result
​
Step 4: Add UI Screen for Loading.
We will use “inProgress” to control loading state of the network calls. When user clicks search icon, then loading screen will show until the network data is received.
Code:
<div if="{{inProgress}}" class="circleAnimation"></div>
<div if="{{inProgress}}" class="circleAnimation"></div>
<div if="{{inProgress}}" class="circleAnimation"></div>
<div if="{{inProgress}}" class="circleAnimation"></div>
<div if="{{inProgress}}" class="circleAnimation"></div>
<div if="{{inProgress}}" class="circleAnimation"></div>
</div>
index.css
Code:
.circleAnimation {
height: 20px;
width: 20px;
margin-left: 20px;
margin-top: 20px;
border-radius: 10;
background-color: red;
animation-name: Stretch;
animation-duration: 1.5s;
animation-timing-function: ease-out;
animation-delay: 0;
animation-iteration-count: infinite;
animation-fill-mode: none;
animation-play-state: running;
}
}
@keyframes spin {
0% { transform: rotate(0); }
100% { transform: rotate(360); }
}
Result
​
Consume REST APIs of openweather.org
We will use two APIs from openweather.org. One is to get the Current weather and other to get the prediction next five day weather. Before using these APIs, Create an account and obtain API Key.
Current weather data
Access current weather data for any location on Earth including over 200,000 cities! We collect and process weather data from different sources such as global and local weather models, satellites, radars and vast network of weather stations. Data is available in JSON, XML, or HTML format.
By city name
You can call by city name or city name, state code and country code.
api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key}
Step 5: Create Model classes for Weather response.
CurrentWeatherResponse.java
Code:
public class CurrentWeatherResponse {
@SerializedName("dt")
private int dt;
@SerializedName("coord")
private Coord coord;
@SerializedName("weather")
private List<WeatherItem> weather;
@SerializedName("name")
private String name;
@SerializedName("cod")
private int cod;
@SerializedName("main")
private Main main;
@SerializedName("clouds")
private Clouds clouds;
@SerializedName("id")
private int id;
@SerializedName("sys")
private Sys sys;
@SerializedName("base")
private String base;
@SerializedName("wind")
private Wind wind;
public int getDt() {
return dt;
}
public void setDt(int dt) {
this.dt = dt;
}
public Coord getCoord() {
return coord;
}
public void setCoord(Coord coord) {
this.coord = coord;
}
public List<WeatherItem> getWeather() {
return weather;
}
public void setWeather(List<WeatherItem> weather) {
this.weather = weather;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCod() {
return cod;
}
public void setCod(int cod) {
this.cod = cod;
}
public Main getMain() {
return main;
}
public void setMain(Main main) {
this.main = main;
}
public Clouds getClouds() {
return clouds;
}
public void setClouds(Clouds clouds) {
this.clouds = clouds;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Sys getSys() {
return sys;
}
public void setSys(Sys sys) {
this.sys = sys;
}
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
public Wind getWind() {
return wind;
}
public void setWind(Wind wind) {
this.wind = wind;
}
}
Daily Forecast 5 Days
Daily Forecast 5 Days is available at any location or city. The forecast includes daily weather data and the response data is available in JSON or XML format
By city name
You can search 5 day weather forecast with daily average parameters by city name. All weather data can be obtained in JSON and XML formats.
api.openweathermap.org/data/2.5/forecast/daily?q={city name}&cnt={cnt}&appid={API key}
Step 6: Create Model classes for Weather response.
MultipleDaysWeatherResponse.java
Code:
public class MultipleDaysWeatherResponse {
@SerializedName("city")
private City city;
@SerializedName("cnt")
private int cnt;
@SerializedName("cod")
private String cod;
@SerializedName("message")
private double message;
@SerializedName("list")
private List<ListItem> list;
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
public String getCod() {
return cod;
}
public void setCod(String cod) {
this.cod = cod;
}
public double getMessage() {
return message;
}
public void setMessage(double message) {
this.message = message;
}
public List<ListItem> getList() {
return list;
}
public void setList(List<ListItem> list) {
this.list = list;
}
}
Step 7: Fetching network data.
We will use simple Java HttpURLConnection for fetching data from Rest APIs. We will have these network operations in Service ability.
WeatherServiceAbility.java
Code:
public class WeatherServiceAbility extends Ability {
private MyRemote remote = new MyRemote();
private static final String CODE = "CODE";
private static final String TEMP = "TEMP";
private static final String HUMIDITY = "HUMIDITY";
private static final String DESCRIPTION = "DESCRIPTION";
private static final String WIND = "WIND";
private static final String CITY_INFO = "CITY_INFO";
private static final String WEATHER_CODE = "WEATHER_CODE";
private static final String MAX_TEMP = "MAX_TEMP";
private static final String MIN_TEMP = "MIN_TEMP";
private static final String FORECAST_URL = "forecast/daily";
private static final String WEATHER_URL = "weather";
private static final String BASE_URI = "https://api.openweathermap.org/data/2.5/";
private static final String API_KEY ="appid={{Add your Key}}";
private static final String UNITS ="units=metric";
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
class MyRemote extends RemoteObject implements IRemoteBroker {
private static final int SUCCESS = 0;
private static final int CURRENT = 1001;
private static final int FORECAST = 1002;
MyRemote() {
super("MyService_MyRemote");
}
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
RequestParam param = getParamFromMessageParcel(data);
switch (code) {
case CURRENT: {
String output = startNetworkCall(BASE_URI + WEATHER_URL, new String[]{"q=" + param.getCity(), UNITS, API_KEY});
CurrentWeatherResponse countryObj = new Gson().fromJson(String.valueOf(output), CurrentWeatherResponse.class);
reply.writeString(bundleSuccessResult(countryObj));
}
break;
case FORECAST: {
String output = startNetworkCall(BASE_URI + FORECAST_URL, new String[]{"q=" + param.getCity(), "cnt=5", UNITS, API_KEY});
MultipleDaysWeatherResponse fiveHistoryObj = new Gson().fromJson(String.valueOf(output), MultipleDaysWeatherResponse.class);
reply.writeString(bundleforPredictedWeather(fiveHistoryObj));
}
break;
default: {
reply.writeString("service not defined");
return false;
}
}
return true;
}
@Override
public IRemoteObject asObject() {
return this;
}
}
private RequestParam getParamFromMessageParcel(MessageParcel message) {
String zsonStr = message.readString();
try {
return new Gson().fromJson(zsonStr, RequestParam.class);
} catch (RuntimeException e) {
}
return null;
}
private String bundleSuccessResult (CurrentWeatherResponse response) {
Map<String, Object> zsonResult = new HashMap<String, Object>();
zsonResult.put(CODE, MyRemote.SUCCESS);
zsonResult.put(TEMP , String.format(Locale.getDefault(), "%.0f°C", response.getMain().getTemp()) );
zsonResult.put(HUMIDITY , response.getMain().getHumidity());
zsonResult.put(WIND , response.getWind().getSpeed());
zsonResult.put(DESCRIPTION , response.getWeather().get(0).getDescription());
zsonResult.put(CITY_INFO , response.getName()+", "+response.getSys().getCountry());
zsonResult.put(WEATHER_CODE , response.getWeather().get(0).getId());
return ZSONObject.toZSONString(zsonResult);
}
private String bundleforPredictedWeather (MultipleDaysWeatherResponse response) {
List<ListItem> list = response.getList();
ZSONArray array = new ZSONArray();
for (ListItem item : list) {
Map<String, Object> zsonResult = new HashMap<String, Object>();
zsonResult.put(MAX_TEMP , item.getTemp().getMax());
zsonResult.put(MIN_TEMP ,item.getTemp().getMin());
zsonResult.put(WEATHER_CODE , (item.getWeather().get(0).getId()));
array.add(zsonResult);
}
return ZSONObject.toZSONString(array);
}
public String startNetworkCall(String link, String params[]) {
NetManager netManager = NetManager.getInstance(null);
if (!netManager.hasDefaultNet()) {
return null;
}
NetHandle netHandle = netManager.getDefaultNet();
// Listen to network state changes.
NetStatusCallback callback = new NetStatusCallback() {
// Override the callback for network state changes.
};
netManager.addDefaultNetStatusCallback(callback);
// Obtain a URLConnection using the openConnection method.
HttpURLConnection connection = null;
try {
StringBuilder urlFinal = new StringBuilder();
urlFinal.append(link);
urlFinal.append("?");
for (int i = 0; i < params.length; i++) {
urlFinal.append("&");
urlFinal.append(params[i]);
}
java.net.URL url = new URL(urlFinal.toString());
URLConnection urlConnection = netHandle.openConnection(url,
java.net.Proxy.NO_PROXY);
if (urlConnection instanceof HttpURLConnection) {
connection = (HttpURLConnection) urlConnection;
}
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb.toString();
}
return null;
} catch (IOException e) {
e.printStackTrace();
return "IOException";
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
Step 8: Display fetched data in UI
Once the user clicks search icon, the city name passed as parameter to async call from JavaScript. Fetch weather method will send the feature ability call to the java layer.
Code:
fetchWeather: async function(city) {
var actionData = {};
actionData.city = ""+city;
var action = {};
action.bundleName = 'com.huawei.phonesample';
action.abilityName = 'com.huawei.phonesample.WeatherServiceAbility ';
action.messageCode = ACTION_MESSAGE_CODE_CURRENT;
action.data = actionData;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;
var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
this.inProgress = false;
this.currentTemperature = ret.TEMP;
this.currentDesc = ret.DESCRIPTION;
this.currentWind = ret.WIND;
this.currentHumidity = ret.HUMIDITY + "%"
this.currentCityInfo = ret.CITY_INFO
this.searchValue = ""
this.updateWeatherArt(ret.WEATHER_CODE)
this.toDay = new Date().getDate() + "-" + month_names[new Date().getMonth()];
if (ret.code == 0) {
console.info('plus result is:' + JSON.stringify(ret.abilityResult));
this.currentTemperature = ret.TEMP + "°";
this.currentDesc = ret.DESCRIPTION;
this.currentWind = ret.WIND;
this.currentHumidity = ret.HUMIDITY + "%"
} else {
console.error('plus error code:' + JSON.stringify(ret.code));
}
},
Once we have data after call ability parse the result to json object and retrieve the data required to display the UI.
Set the inProgress flag also to false to update the UI section with data.
Update weather icon checking the weather code.
Code:
updateWeatherArt(weatherCode) {
if (weatherCode / 100 == 2) {
this.artImage = "/common/art_storm.png";
} else if (weatherCode / 100 == 3) {
this.artImage = "/common/art_rain.png";
} else if (weatherCode / 100 == 5) {
this.artImage = "/common/art_light_rain.png";
} else if (weatherCode / 100 == 6) {
this.artImage = "/common/art_snow.png";
} else if (weatherCode / 100 == 7) {
this.artImage = "/common/art_clear.png";
} else if (weatherCode == 800) {
this.artImage = "/common/art_clear.png";
} else if (weatherCode == 801) {
this.artImage = "/common/art_light_clouds.png";
} else if (weatherCode == 803) {
this.artImage = "/common/art_light_clouds.png";
} else if (weatherCode / 100 == 8) {
this.artImage = "/common/art_clouds.png";
}
}
Similarly update the five day data for the second UI section.
Tips and Tricks
You can use Cloud emulator for development. I have explained UI updating for current weather, but similarly you can update the carousel UI with array object you got from second API response. There are few more options like fetching the current location and use that for getting the weather, which will be an added feature.
Conclusion
In this article, we have learnt how to create weather application using HarmonyOS UI components and Service Ability. We have explored Serviceability for fetching data from open source REST API.
References
JS API References
Weather API
Original Source

Intermediate: Integration of Huawei map kit and Location kit in DeliveryApp in Flutter (Cross platform)

Introduction​In this article, we will be integrating Huawei Map kit and Location kit in Food Delivery application. Huawei Map kit currently allows developer to create map, interactions with map and drawing on a map.
We will be covering all three aspects as the delivery application we need to create map and we need to draw polyline from delivery agent location to user location and on interaction also we are providing i.e. on click the marker we are show popup on the map with details as shown in the result section below.
Development Overview​You need to install Flutter and Dart plugin in IDE and I assume that you have prior knowledge about the Flutter and Dart.
Hardware Requirements​
A computer (desktop or laptop) running Windows 10.
A Huawei phone (with the USB cable), which is used for debugging.
Software Requirements​
Java JDK 1.7 or later.
Android studio software or Visual Studio or Code installed.
HMS Core (APK) 4.X or later.
Integration process​Step 1. Create flutter project
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Step 2. Add the App level gradle dependencies. Choose inside project Android > app > build.gradle.​
Code:
apply plugin:'com.huawei.agconnect'
Add root level gradle dependencies.
Code:
maven {url 'https://developer.huawei.com/repo/'}
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
Add app level gradle dependencies.
Code:
implementation 'com.huawei.hms:maps:5.0.3.302'
implementation 'com.huawei.hms:location:5.0.0.301'
Step 3: Add the below permissions in Android Manifest file.
Code:
<uses-permission android:name="android.permission.INTERNET " />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION"/>
Step 4: Add below path in pubspec.yaml file under dependencies.
Step 5 : Create a project in AppGallery Connect.​pubspec.yaml​
Code:
name: sample_one
description: A new Flutter application.
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
huawei_map:
path: ../huawei_map/
huawei_location:
path: ../huawei_location/
http: ^0.12.2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
How to check required permissions are granted or not?​
Code:
void hasPermission() async {
try {
bool status = await permissionHandler.hasLocationPermission();
setState(() {
message = "Has permission: $status";
if (status) {
getLastLocationWithAddress();
//requestLocationUpdatesByCallback();
} else {
requestPermission();
}
});
} catch (e) {
setState(() {
message = e.toString();
});
}
}
How do I request permission?​
Code:
void requestPermission() async {
try {
bool status = await permissionHandler.requestLocationPermission();
setState(() {
message = "Is permission granted $status";
});
} catch (e) {
setState(() {
message = e.toString();
});
}
}
How do I get location data?​
Code:
void getLastLocationWithAddress() async {
try {
HWLocation location =
await locationService.getLastLocationWithAddress(locationRequest);
setState(() {
message = location.street +
" " +
location.city +
" " +
location.state +
" " +
location.countryName +
" " +
location.postalCode;
print("Location: " + message);
});
} catch (e) {
setState(() {
message = e.toString();
print(message);
});
}
}
main.dart​
Code:
import 'package:flutter/material.dart';
import 'package:huawei_map/map.dart';
import 'package:sample_one/mapscreen2.dart';
import 'package:sample_one/order.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Orders'),
),
body: MyApp(),
),
debugShowCheckedModeBanner: false,
);
}
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List orders = [
Order(
imageUrl:
"https://www.namesnack.com/images/namesnack-pizza-business-names-5184x3456-20200915.jpeg",
name: "Veg Pizza Special",
username: "Naresh K",
location: new LatLng(12.9698, 77.7500)),
Order(
imageUrl:
"https://www.pizzahutcouponcode.com/wp-content/uploads/2020/12/10.jpg",
name: "Pretzel Rolls ",
username: "Ramesh",
location: new LatLng(12.9698, 77.7500)),
Order(
imageUrl:
"https://www.manusmenu.com/wp-content/uploads/2015/01/1-Chicken-Spring-Rolls-9-1-of-1.jpg",
name: "Special Veg Rolls",
username: "Mahesh N",
location: new LatLng(12.9598, 77.7540)),
Order(
imageUrl:
"https://www.thespruceeats.com/thmb/axBJnjZ_30_-iHgjGzP1tS4ssGA=/4494x2528/smart/filters:no_upscale()/thai-fresh-rolls-with-vegetarian-option-3217706_form-rolls-step-07-f2d1c96942b04dd0830026702e697f17.jpg",
name: "The Great Wall of China",
username: "Chinmay M",
location: new LatLng(12.9098, 77.7550)),
Order(
imageUrl:
"https://cdn.leitesculinaria.com/wp-content/uploads/2021/02/pretzel-rolls-fp.jpg.optimal.jpg",
name: "Pretzel Rolls",
username: "Ramesh",
location: new LatLng(12.9658, 77.7400)),
Order(
imageUrl:
"https://dinnerthendessert.com/wp-content/uploads/2019/01/Egg-Rolls-3.jpg",
name: "Egg Rolls",
username: "Preeti",
location: new LatLng(12.9618, 77.7700)),
Order(
imageUrl:
"https://images.immediate.co.uk/production/volatile/sites/30/2020/08/recipe-image-legacy-id-1081476_12-9367fea.jpg",
name: "Easy Spring Rolls",
username: "Nithin ",
location: new LatLng(12.9218, 77.7100)),
];
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white60,
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 1),
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.network(orders[index].imageUrl),
title: Text(orders[index].name),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => MapPage(
orders[index].name, orders[index].location)));
},
subtitle: Text(orders[index].username),
);
},
),
),
],
),
),
),
);
}
}
mapscreen.dart​
Code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:huawei_map/map.dart';
import 'package:sample_one/directionapiutil.dart';
import 'package:sample_one/routerequest.dart';
import 'package:sample_one/routeresponse.dart';
class MapPage extends StatefulWidget {
String name;
LatLng location;
MapPage(this.name, this.location);
@override
_MapPageState createState() => _MapPageState(name, location);
}
class _MapPageState extends State<MapPage> {
String name, dist = '';
LatLng location, dest_location = new LatLng(12.9709, 77.7257);
_MapPageState(this.name, this.location);
HuaweiMapController _mapController;
final Set<Marker> _markers = {};
final Set<Polyline> _polyLines = {};
final List<LatLng> _points = [];
BitmapDescriptor _markerIcon;
List<LatLng> polyList = [
LatLng(12.9970, 77.6690),
LatLng(12.9569, 77.7011),
LatLng(12.9177, 77.6238)
];
@override
void initState() {
super.initState();
_loadMarkers(location);
showDirection();
}
@override
Widget build(BuildContext context) {
//_customMarker(context);
return new Scaffold(
appBar: null,
body: Stack(
children: [
_buildMap(),
Positioned(
top: 10,
right: 40,
left: 40,
child: ButtonBar(
buttonPadding: EdgeInsets.all(15),
alignment: MainAxisAlignment.center,
children: <Widget>[
/* new RaisedButton(
onPressed: showDirection,
child: new Text("Show direction",
style: TextStyle(fontSize: 20.0)),
color: Colors.green,
),*/
Center(
child: new Text(
"$dist",
style:
TextStyle(fontSize: 20.0, backgroundColor: Colors.cyan),
),
),
/* new RaisedButton(
onPressed: _showPolygone,
child: new Text("Polygon",
style: TextStyle(fontSize: 20.0, color: Colors.white)),
color: Colors.lightBlueAccent,
),*/
],
),
)
],
),
);
}
_buildMap() {
return HuaweiMap(
initialCameraPosition: CameraPosition(
target: location,
zoom: 12.0,
bearing: 30,
),
onMapCreated: (HuaweiMapController controller) {
_mapController = controller;
},
mapType: MapType.normal,
tiltGesturesEnabled: true,
buildingsEnabled: true,
compassEnabled: true,
zoomControlsEnabled: true,
rotateGesturesEnabled: true,
myLocationButtonEnabled: true,
myLocationEnabled: true,
trafficEnabled: true,
markers: _markers,
polylines: _polyLines,
onClick: (LatLng latlong) {
setState(() {
//createMarker(latlong);
});
},
);
}
void showRouteBetweenSourceAndDestination(
LatLng sourceLocation, LatLng destinationLocation) async {
RouteRequest request = RouteRequest(
origin: LocationModel(
lat: sourceLocation.lat,
lng: sourceLocation.lng,
),
destination: LocationModel(
lat: destinationLocation.lat,
lng: destinationLocation.lng,
),
);
try {
RouteResponse response = await DirectionUtils.getDirections(request);
setState(() {
drawRoute(response);
dist = response.routes[0].paths[0].distanceText;
});
} catch (Exception) {
print('Exception: Failed to load direction response');
}
}
drawRoute(RouteResponse response) {
if (_polyLines.isNotEmpty) _polyLines.clear();
if (_points.isNotEmpty) _points.clear();
var steps = response.routes[0].paths[0].steps;
for (int i = 0; i < steps.length; i++) {
for (int j = 0; j < steps[i].polyline.length; j++) {
_points.add(steps[i].polyline[j].toLatLng());
}
}
setState(() {
_polyLines.add(
Polyline(
width: 2,
polylineId: PolylineId("route"),
points: _points,
color: Colors.blueGrey),
);
/*for (int i = 0; i < _points.length - 1; i++) {
totalDistance = totalDistance +
calculateDistance(
_points[i].lat,
_points[i].lng,
_points[i + 1].lat,
_points[i + 1].lng,
);
}*/
});
}
void _loadMarkers(LatLng location) {
if (_markers.length > 0) {
setState(() {
_markers.clear();
});
} else {
setState(() {
_markers.add(Marker(
markerId: MarkerId('marker_id_1'),
position: location,
icon: _markerIcon,
infoWindow: InfoWindow(
title: 'Delivery agent',
snippet: 'location',
),
rotation: 5));
_markers.add(Marker(
markerId: MarkerId('marker_id_2'),
position: dest_location,
draggable: true,
icon: _markerIcon,
clickable: true,
infoWindow: InfoWindow(
title: 'User',
snippet: 'location',
),
rotation: 5));
});
}
}
void _customMarker(BuildContext context) async {
if (_markerIcon == null) {
final ImageConfiguration imageConfiguration =
createLocalImageConfiguration(context);
BitmapDescriptor.fromAssetImage(
imageConfiguration, 'assets/images/icon.png')
.then(_updateBitmap);
}
}
void _updateBitmap(BitmapDescriptor bitmap) {
setState(() {
_markerIcon = bitmap;
});
}
void createMarker(LatLng latLng) {
Marker marker;
marker = new Marker(
markerId: MarkerId('Welcome'),
position: LatLng(latLng.lat, latLng.lng),
icon: BitmapDescriptor.defaultMarker);
setState(() {
_markers.add(marker);
});
}
void remove() {
setState(() {
_markers.clear();
});
}
showDirection() {
Future.delayed(const Duration(seconds: 1), () {
//setState(() {
showRouteBetweenSourceAndDestination(location, dest_location);
//});
});
}
}
Result​
Tips and Tricks
Make sure you have downloaded latest plugin.
Make sure that updated plugin path in yaml.
Make sure that plugin unzipped in parent directory of project.
Makes sure that agconnect-services.json file added.
Make sure dependencies are added build file.
Run flutter pug get after adding dependencies.
Generating SHA-256 certificate fingerprint in android studio and configure in Ag-connect.
Conclusion​In this article, we have learnt how to integrate Huawei Map kit and Location kit in Flutter for the DeliveryApp, where application gets the list of orders and delivery agent click on the order to navigate to map. Similar way you can use Huawei Map kit as per user requirement in your application.
Thank you so much for reading, I hope this article helps you to understand the Huawei Map kit and Location kit in flutter.
References​Flutter map
Flutter plugin
Location Kit
Original Source
What are all the different types of maps it will supports?
can we implement start navigation feature like google map feature?

Huawei Smart Watch – Fetch Location to make Prayer times calculation Application Development using JS on HUAWEI DevEco Studio (HarmonyOS)

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Article Introduction
In this article we will develop Prayer Times application for Huawei Smart Watch device using Huawei DevEco Studio (HarmonyOS). We will fetch Location using HarmonyOS JS language API’s and use some of the npm libraries (adhan, moment, moment-timezone, tz-lookup) to develop complete Real world Prayer Times Calculation Application.
1. Create New Project
Let’s create Smart Watch Project and choosing ability template, Empty Ability (JS)
Define project name, package name and relevant directory where you want to save your project. Choose the Device type “wearable” for which we are developing the application.
2. Preparing Files and Permission
Let’s first add images and permissions which we will use for project.
All project images will be under common/images folder, check below screenshot.
Next we need to add Location and Internet permissions under config.json file.
Code:
"reqPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.LOCATION",
"reason": "get user location to show prayer time",
"usedScene": {
"ability": [
"default"
],
"when": "always"
}
}
]
3. NPM libraries installation
We need to install following NPM libraries in the application:
adhan
moment
moment-timezone
tz-lookup
First we need to open the terminal under our DevEco studio project.
We need to change directory to entry folder.
Code:
cd entry
Now we need to install all the required libraries for our project.
Code:
npm i adhan moment moment-timezone tz-lookup -s
After installation our package.json file look like below:
Code:
{
"dependencies": {
"adhan": "^4.1.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"tz-lookup": "^6.1.25"
}
}
4. Prayer Time App Development
In Prayer time screen development we will cover Location permission, Location fetching, location error layout, prayer timer screen and today all prayers dialog screen.
Let’s start development without wasting more time.
Styling:
index.css: (Common screen styling)
Code:
/* common styling */
.container {
background-color: black;
justify-content: center;
}
.container-sub {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
padding-top: 24px;
}
.container-location-loading {
flex-direction: column;
padding-top: 0px;
padding-bottom: 0px;
height: 456px;
width: 456px;
}
.column {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
background-color: transparent;
}
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 80%;
height: 25px;
background-color: transparent;
}
.title {
text-align: center;
display: flex;
font-size: 16px;
}
.center {
text-align: center;
}
.location_loading {
object-fit: contain;
height: 456px;
width: 240px;
text-align: center;
align-items: center;
}
.current_time {
font-size: 18px;
text-align: center;
}
.mosque {
margin-top: 5px;
text-align: center;
fit-original-size: true;
}
.prayer_name {
text-align: center;
font-size: 16px;
margin-top: 2px;
margin-bottom: 5px;
}
.remaining_timer {
text-align: center;
font-size: 14px;
}
.button-circle {
background-color: transparent;
}
index.css: (Prayer BG & Color styling)
Code:
/* prayer BG & Color */
.prayer_bg {
background-position: top center;
background-size: 100% 280px;
}
.fajr_bg {
background-image: url('/common/images/prayer_bg/fajr.jpg');
}
.fajr_color {
background-color: #30170d;
}
.dhuhr_bg {
background-image: url('/common/images/prayer_bg/dhuhr.jpg');
}
.dhuhr_color {
background-color: #021823;
}
.asr_bg {
background-image: url('/common/images/prayer_bg/asr.jpg');
}
.asr_color {
background-color: #172B34;
}
.maghrib_bg {
background-image: url('/common/images/prayer_bg/maghrib.jpg');
}
.maghrib_color {
background-color: #010101;
}
.isha_bg {
background-image: url('/common/images/prayer_bg/isha.jpg');
}
.isha_color {
background-color: #082C44;
}
.night_bg {
background-image: url('/common/images/prayer_bg/night.jpg');
}
.night_color {
background-color: #131C39;
}
index.css: (Dialog styling)
Code:
/*Dialog styling*/
.dialog-main {
width: 100%;
}
.dialog-div {
display: flex;
flex-direction: column;
align-items: center;
}
.inner-txt {
width: 100%;
height: 300px;
flex-direction: column;
align-items: center;
}
.inner-btn {
width: 100%;
height: 154px;
align-items: center;
}
index.css: (List styling)
Code:
/*list styling*/
.list-wrapper {
width: 100%;
flex-direction: column;
}
.list-items {
width: 100%;
flex-direction: column;
padding: 0 24px;
}
.item-wrapper {
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
height: 34px;
margin: 8px 0;
}
.item-icon-wrapper {
width: 24px;
}
.item-icon {
width: 24px;
height: 24px;
object-fit: contain;
}
.item-name-description-wrapper {
flex-direction: column;
justify-content: center;
align-items: center;
flex-grow: 1;
flex-shrink: 1;
width: 50%;
margin-right: 24px;
margin-left: 24px;
}
.item-name {
text-align: left;
color: #DBFFFFFF;
font-size: 16px;
}
.item-description {
text-align: left;
opacity: 0.75;
color: #99FFFFFF;
font-size: 14px;
}
.item-right-part-wrapper {
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.item-right-text {
margin-right: 4px;
margin-left: 8px;
font-size: 14px;
opacity: 0.75;
}
.item-right-arrow {
width: 12px;
height: 24px;
object-fit: contain;
}
.line {
stroke-width: 1px;
width: 100%;
background-color: #33FFFFFF;
margin-left: 40px;
}
index.css: (Birds animation styling)
Code:
/* Birds animation */
.birds_animation {
object-fit: scale-down;
position: absolute;
top: 0px;
left: -200px;
animation-name: Fly;
animation-duration: 15s;
animation-timing-function: ease;
animation-iteration-count: infinite;
}
@keyframes Fly {
from {
transform: translateX(-200px);
}
to {
transform: translateX(1000px);
}
}
Layout:
Index.hml: (Location Loading Animation)
Code:
<div if="{{ isLocationLoading === true }}" class="container-location-loading">
<image src="common/images/location_animation.gif" class="location_loading"/>
</div>
Index.hml: (Location Loading Output):
Index.hml: (Location Error & Retry)
Code:
<div class="column" if="{{ isLocationLoading === false && isLocationError === true }}">
<text class="title">Location not fetch, please try again later.</text>
</div>
Index.hml: (Prayer timer UI)
Code:
<div class="container-sub prayer_bg {{ prayer_bg }}" if="{{ isLocationLoading === false && isLocationError === false }}">
<image src="common/images/birds.gif" class="birds_animation"></image>
<text class="current_time">{{ currentTime }}</text>
<image class="mosque" src="common/images/mosque.png"></image>
<text class="prayer_name">{{nextPrayer}} {{nextPrayerTime}}</text>
<text if="{{isShowTargetTime}}" class="remaining_timer">{{nextPrayerRemaining}}</text>
<button type="circle" class="button-circle"
ontouchend="showPrayer" icon="common/images/down-arrow.png"></button>
</div>
Index.hml: (Prayer timer UI Output)
Index.hml: (Dialog all Prayer times)
Code:
<dialog id="simpledialog" class="dialog-main">
<div class="dialog-div {{ dialog_bg }}">
<button type="circle" class="button-circle"
ontouchend="closePrayer" icon="common/images/close.png"></button>
<div class="inner-txt">
<div class="prayers-list">
<div class="list-items-left">
<list class="list-wrapper" initialindex="{{ initial_index_value }}">
<block for="{{ prayer_data }}">
<list-item class="list-items" @click="changeList($idx)" id="{{ $idx }}">
<div class="item-wrapper">
<div class="item-icon-wrapper">
<image class="item-icon" src="{{ $item.item_icon }}"></image>
</div>
<div class="item-name-description-wrapper">
<text class="item-name">{{ $item.item_name }}</text>
<text class="item-description">{{ $item.item_description }}</text>
</div>
<div class="item-right-part-wrapper">
<image class="item-right-arrow" src="common/images/right_arrow_dark_mode.png"></image>
</div>
</div>
<div class="divider-line">
<divider class="line"></divider>
</div>
</list-item>
</block>
</list>
</div>
</div>
</div>
</div>
</dialog>
Index.hml: (Dialog all Prayer times Ouput)
Index.hml: (Complete code)
Code:
<div class="container {{ (isLocationLoading === false) ? 'column' : '' }}">
<div if="{{ isLocationLoading === true }}" class="container-location-loading">
<image src="common/images/location_animation.gif" class="location_loading"/>
</div>
<div class="column" if="{{ isLocationLoading === false && isLocationError === true }}">
<text class="title">Location not fetch, please try again later.</text>
</div>
<div class="container-sub prayer_bg {{ prayer_bg }}" if="{{ isLocationLoading === false && isLocationError === false }}">
<image src="common/images/birds.gif" class="birds_animation"></image>
<text class="current_time">{{ currentTime }}</text>
<image class="mosque" src="common/images/mosque.png"></image>
<text class="prayer_name">{{nextPrayer}} {{nextPrayerTime}}</text>
<text if="{{isShowTargetTime}}" class="remaining_timer">{{nextPrayerRemaining}}</text>
<button type="circle" class="button-circle"
ontouchend="showPrayer" icon="common/images/down-arrow.png"></button>
</div>
<dialog id="simpledialog" class="dialog-main">
<div class="dialog-div {{ dialog_bg }}">
<button type="circle" class="button-circle"
ontouchend="closePrayer" icon="common/images/close.png"></button>
<div class="inner-txt">
<div class="prayers-list">
<div class="list-items-left">
<list class="list-wrapper" initialindex="{{ initial_index_value }}">
<block for="{{ prayer_data }}">
<list-item class="list-items" @click="changeList($idx)" id="{{ $idx }}">
<div class="item-wrapper">
<div class="item-icon-wrapper">
<image class="item-icon" src="{{ $item.item_icon }}"></image>
</div>
<div class="item-name-description-wrapper">
<text class="item-name">{{ $item.item_name }}</text>
<text class="item-description">{{ $item.item_description }}</text>
</div>
<div class="item-right-part-wrapper">
<image class="item-right-arrow" src="common/images/right_arrow_dark_mode.png"></image>
</div>
</div>
<div class="divider-line">
<divider class="line"></divider>
</div>
</list-item>
</block>
</list>
</div>
</div>
</div>
</div>
</dialog>
</div>
JS code:
index.js: (Structural - Code)
Code:
import geolocation from '@system.geolocation';
import adhan from 'adhan';
import moment from 'moment';
import tz from 'moment-timezone';
var tzlookup = require("tz-lookup");
const TAG = 'app_log [index]';
export default {}
index.js: (Data - Code)
Code:
data: {
config: {
isTesting: true,
locationCoordinates: {
"latitude": 24.65382908421087,
"longitude": 46.73552629355017
},
timeZone: "Asia/Riyadh",
fakeDateTime: "2021-06-12 18:13:01"
},
prayer_data: [
{
item_id: "fajr",
item_icon: "common/images/prayer_icon/fajr.png",
item_name: 'Fajr',
item_description: ''
},
{
item_id: "dhuhr",
item_icon: "common/images/prayer_icon/dhuhr.png",
item_name: 'Dhuhr',
item_description: ''
},
{
item_id: "asr",
item_icon: "common/images/prayer_icon/asr.png",
item_name: 'Asr',
item_description: ''
},
{
item_id: "maghrib",
item_icon: "common/images/prayer_icon/maghrib.png",
item_name: 'Maghrib',
item_description: ''
},
{
item_id: "isha",
item_icon: "common/images/prayer_icon/isha.png",
item_name: 'Isha',
item_description: ''
},
],
defaultPrayerSetting: {
allowNotification: false,
prayerSetting: {
Madhab: 'Shafi',
calculationMethod: 'UmmAlQura',
adjustments: {
fajr: "0",
sunrise: "0",
dhuhr: "0",
asr: "0",
maghrib: "0",
isha: "0"
}
}
},
initial_index_value: 2,
isLocationError: false,
isLocationLoading: true,
locationCoordinates: null,
currentTime: null,
timeUpdateTimer: null,
nextPrayer: 'Night',
nextPrayerTime: '',
nextPrayerRemaining: '',
isShowTargetTime: true,
date: moment().toDate(),
prayer_bg: "night_bg",
dialog_bg: "night_color"
},
index.js: (Common - Code)
Code:
onInit() {
console.log(TAG + 'onInit');
if(this.config.isTesting === true){
this.locationCoordinates = this.config.locationCoordinates
moment.tz.setDefault(this.config.timeZone);
this.date = moment(this.config.fakeDateTime).toDate();
}
this.currentTime = moment(this.date).format('ddd LT');
this.timeUpdateTimer = setInterval(this.updateTimer, 2000);
},
onReady() {
console.log(TAG + 'onReady');
var _this = this;
if (this.locationCoordinates !== null) {
setTimeout(() => {
_this.calculatePrayerTime();
_this.isLocationLoading = false;
_this.isLocationError = false;
}, 4000);
} else {
this.locationLoading().then(result => {
_this.locationCoordinates = result;
console.info(TAG + "Location: " + result);
_this.calculatePrayerTime();
_this.isLocationLoading = false;
_this.isLocationError = false;
}, error => {
console.info(TAG + "Location: error ->" + error);
_this.isLocationLoading = false;
_this.isLocationError = true;
});
}
},
onShow() {
console.log(TAG + 'onShow');
},
onDestroy() {
console.log(TAG + 'onDestroy');
clearInterval(this.timeUpdateTimer);
this.timeUpdateTimer = null;
clearInterval(this.countDownTimer);
this.countDownTimer = null;
},
updateTimer() {
this.currentTime = moment().format('ddd LT');
if(this.config.isTesting === true){
this.currentTime = moment(this.config.fakeDateTime).format('ddd LT');
}
},
index.js: (Dialog - Code)
Code:
showPrayer(e) {
this.$element('simpledialog').show();
},
closePrayer(e) {
this.$element('simpledialog').close();
},
index.js: (Location Fetching - Code)
Code:
locationLoading() {
return new Promise(function (resolve, reject) {
return geolocation.getLocation({
success: function (data) {
console.log('success get location data. latitude:' + data.latitude + 'long:' + data.longitude);
return resolve({
latitude: data.latitude,
longitude: data.longitude
});
},
fail: function (data, code) {
console.log('fail to get location. code:' + code + ', data:' + data);
return reject({
error: 'fail to get location. code:' + code + ', data:' + data
});
},
});
});
},
index.js: (Prayer times - Code)
Code:
calculatePrayerTime() {
var _this = this;
var prayerSettings = this.defaultPrayerSetting;
console.log(TAG + 'prayer_setting: getPrayerSetting() ' + JSON.stringify(prayerSettings));
if (prayerSettings !== null) {
this.prayerSettings = prayerSettings;
var params = this.getPrayerParameter(this.prayerSettings);
var coordinates = new adhan.Coordinates(_this.locationCoordinates.latitude, _this.locationCoordinates.longitude);
var date = this.date;
var prayerTimes = new adhan.PrayerTimes(coordinates, date, params);
console.info(TAG + 'locationCoordinates ' + JSON.stringify(_this.locationCoordinates));
var timezone = tzlookup(_this.locationCoordinates.latitude, _this.locationCoordinates.longitude)
if(this.config.isTesting === true){
timezone = this.config.timeZone
}
console.log(TAG + "timezone: " + timezone);
var nextPrayer = prayerTimes.nextPrayer(date);
var currentPrayer = prayerTimes.currentPrayer(date);
console.info(TAG + 'nextPrayer ' + nextPrayer);
console.info(TAG + 'currentPrayer ' + currentPrayer);
if (nextPrayer.toString() === "none") {
_this.isShowTargetTime = false
_this.nextPrayer = "Night";
_this.managePrayerTime(prayerTimes, timezone, nextPrayer, currentPrayer)
} else {
_this.isShowTargetTime = true
_this.nextPrayer = nextPrayer;
var nextPrayerTime = prayerTimes.timeForPrayer(nextPrayer);
_this.nextPrayerTime = moment(nextPrayerTime).tz(timezone).format('h:mm A');
_this.setTimeInfo(nextPrayerTime.getTime());
_this.managePrayerTime(prayerTimes, timezone, nextPrayer, currentPrayer)
}
}
},
managePrayerTime(prayerTimes, timezone, nextPrayer, currentPrayer) {
var _this = this;
var fajrTime = moment(prayerTimes.fajr).tz(timezone).format('h:mm A');
var sunriseTime = moment(prayerTimes.sunrise).tz(timezone).format('h:mm A');
var dhuhrTime = moment(prayerTimes.dhuhr).tz(timezone).format('h:mm A');
var asrTime = moment(prayerTimes.asr).tz(timezone).format('h:mm A');
var maghribTime = moment(prayerTimes.maghrib).tz(timezone).format('h:mm A');
var ishaTime = moment(prayerTimes.isha).tz(timezone).format('h:mm A');
_this.prayer_data.map(item => {
if (item.item_id === "fajr") {
item.item_description = fajrTime;
}
if (item.item_id === "dhuhr") {
item.item_description = dhuhrTime;
}
if (item.item_id === "asr") {
item.item_description = asrTime;
}
if (item.item_id === "maghrib") {
item.item_description = maghribTime;
}
if (item.item_id === "isha") {
item.item_description = ishaTime;
}
if (nextPrayer.toString().toLowerCase() === item.item_id) {
_this.prayer_bg = item.item_id + "_bg";
_this.dialog_bg = item.item_id + "_color";
}
});
},
getPrayerParameter(prayerSettings) {
var params = adhan.CalculationMethod.UmmAlQura();
var prayerSetting = prayerSettings.prayerSetting;
if (prayerSetting.calculationMethod === 'MuslimWorldLeagueMuslimWorldLeague') {
params = adhan.CalculationMethod.MuslimWorldLeague();
} else if (prayerSetting.calculationMethod === 'Egyptian') {
params = adhan.CalculationMethod.Egyptian();
} else if (prayerSetting.calculationMethod === 'Karachi') {
params = adhan.CalculationMethod.Karachi();
} else if (prayerSetting.calculationMethod === 'Dubai') {
params = adhan.CalculationMethod.Dubai();
} else if (prayerSetting.calculationMethod === 'MoonsightingCommittee') {
params = adhan.CalculationMethod.MoonsightingCommittee();
} else if (prayerSetting.calculationMethod === 'NorthAmerica') {
params = adhan.CalculationMethod.NorthAmerica();
} else if (prayerSetting.calculationMethod === 'Kuwait') {
params = adhan.CalculationMethod.Kuwait();
} else if (prayerSetting.calculationMethod === 'Qatar') {
params = adhan.CalculationMethod.Qatar();
} else if (prayerSetting.calculationMethod === 'Singapore') {
params = adhan.CalculationMethod.Singapore();
} else if (prayerSetting.calculationMethod === 'Other') {
params = adhan.CalculationMethod.Other();
}
if (prayerSetting.Madhab === 'Shafi') {
params.madhab = adhan.Madhab.Shafi;
} else {
params.madhab = adhan.Madhab.Hanafi;
}
params.adjustments.fajr = parseInt(prayerSetting.adjustments.fajr) || 0;
params.adjustments.sunrise = parseInt(prayerSetting.adjustments.sunrise) || 0;
params.adjustments.dhuhr = parseInt(prayerSetting.adjustments.dhuhr) || 0;
params.adjustments.asr = parseInt(prayerSetting.adjustments.asr) || 0;
params.adjustments.maghrib = parseInt(prayerSetting.adjustments.maghrib) || 0;
params.adjustments.isha = parseInt(prayerSetting.adjustments.isha) || 0;
return params;
},
index.js: (Count down timer - Code)
Code:
setTimeInfo(next_time) {
console.log(TAG + "next_time: " + next_time);
this.CaculateTime(next_time);
this.countDownTimer = setInterval(() => {
this.CaculateTime(next_time);
}, 1000);
},
CaculateTime(timeObj) {
var myDate = new Date();
if (this.config.isTesting === true) {
this.date = moment(this.date).add(500, 'milliseconds').toDate();
myDate = this.date;
}
let currentTime = myDate.getTime();
var targetTime = parseInt(timeObj);
var remainTime = parseInt(targetTime - currentTime);
if (remainTime > 0) {
this.isShowTargetTime = true;
this.setRemainTime(remainTime);
//this.setTargetTime(targetTime);
}
},
setRemainTime(remainTime) {
let days = this.addZero(Math.floor(remainTime / (24 * 3600 * 1000))); // Calculate the number of days
let leavel = remainTime % (24 * 3600 * 1000); // Time remaining after counting days
let hours = this.addZero(Math.floor(leavel / (3600 * 1000))); // Calculate the number of hours remaining
let leavel2 = leavel % (3600 * 1000); // Number of milliseconds remaining after calculating the remaining hours
let minutes = this.addZero(Math.floor(leavel2 / (60 * 1000))); // Calculate the number of minutes remaining
// Calculate the difference seconds.
let leavel3 = leavel2 % (60 * 1000); // Number of milliseconds remaining after minutes are calculated
let seconds = this.addZero(Math.round(leavel3 / 1000));
this.nextPrayerRemaining = hours + ':' + minutes + ':' + seconds;
},
setTargetTime(targetTime) {
var _this = this
var times = new Date(targetTime);
let date = times.toLocaleDateString(); //Gets the current date
var tempSetHours = times.getHours(); //Gets the current number of hours (0 - 23)
let hours = this.addZero(tempSetHours)
var tempSetMinutes = times.getMinutes(); //Gets the current number of minutes (0 - 59)
let minutes = this.addZero(tempSetMinutes)
var tempSetSeconds = times.getSeconds(); //Gets the current number of seconds (0 - 59)
let seconds = this.addZero(tempSetSeconds)
this.targetTime = `${hours}:${minutes}:${seconds}`;
},
addZero: function (i) {
return i < 10 ? "0" + i : i + "";
},
Prayer time Screen Notes:
To manage different state of application on single screen, we can able to use logic layouts using if=”true/false” or show=”true/false” conditions on containers.
For testing of custom date and time developer need to modify config data variable (isTesting: true).
For production we need to apply isTesting: false and relay on real date and time.
For prayer time parameter we implement adhan npm libraries, developer can have access on prayer time adjustment (plus/minus) in minutes.
For better management of prayer time parameters always use local storage (key/value), to save user preferences in storage and adjust prayer time.
5. Result
Tips & Tricks:
HarmonyOS JS project while installing any NPM libraries, in terminal must be on entry folder of your project module.
For testing of any date and time, developer need to modify config data variable (isTesting: true)
For production or realtime device, developer need to modify config data variable (isTesting: false)
For prayer time adjustment developer can modify defaultPrayerSetting data variable and could store user preference in storage.
Requesting some data from internet, you must need to add Internet permission in config.json file.
Fetching Location data, you must need to add Internet Permission in config.json file.
Use Dev Eco Studio Previewer to check the screen layout and design. Previewer is developer friendly to Hot release changes on fly.
For better management of big application it’s a good practice to centralize you common scripts and common style in common folder. Add images folder for complete application images.
In JS script when you make some variable, in callback functions you can store the reference of this to some variable and then call reference variable. Like var _this = this.
References:
HarmonyOS JS API Official Documentation: https://developer.harmonyos.com/en/docs/documentation/doc-references/js-apis-overview-0000001056361791
Geographic Location Documentation: https://developer.harmonyos.com/en/docs/documentation/doc-references/js-apis-system-location-0000000000626089
Conclusion:
Developers can able to develop real world Prayer time calculation application, while fetch user location data and using npm ready-made libraries. While developing application for HarmonyOS developer can get benefit for both JS and JAVA language. Benefit for developing JS based HarmonyOS application developer can able to use npm based libraries and can reduce development time.
Original Source
Useful sharing, thanks!!
Very useful. Thanks for sharing

Categories

Resources