#how to use gridlayout in android
Explore tagged Tumblr posts
eiheducation · 2 years ago
Text
How to get started with LinearLayout on Android Studio with App?
LinearLayout is among the most important layout managers used in Android Studio. It is employed to lay out the layout's content. It is used by default to arrange the content of Activity and Fragment layout. For beginners, it is sufficient to be able to use it but for advanced developers, it's recommended to use other layout managers like GridLayout and FrameLayout. LinearLayout is also sufficient to design the layout of your application. In this article, I'll describe the fundamental procedures and configurations that you have to be aware of to begin working with LinearLayout.
The 5 Best CCNA Certification Books for 2022
1. What is LinearLayout?
LinearLayout is a layout manager used to build the scrolling list of views. It is similar to a ListView or RecyclerView however, it's not a replacement for these. It's an adaptable layout manager that can be used to create footers and headers as well as navigation or other type of content.
CCNA Routing And Switching All In One Study Guide BOOK
2. Basic steps of using LinearLayout
LinearLayout LinearLayout class is among the most effective layout classes in Android. It can be used to create almost any layout you can think of. One of the most frequent uses of LinearLayout could be creating a scrolling layout that behaves like a list. This layout can be made by creating a LinearLayout using only one child, which is an a ListView. This is the basic design of the scrolling list. You can also create an a scrolling list with TabLayout. TabLayout class. This layout is more suitable to create a list of tabs. LinearLayout is a crucial component of the Android framework. It can be used to create various layouts, such as lists that scroll. This article will show you how to start using LinearLayout using Android Studio.
How To Configure OSPF Single Area On 4 Routers In Cisco Packet Tracer
3. Fundamental configurations and configurations to LinearLayout
This section demonstrates how to make use of the LinearLayout to build the layout with just one column and only one row. The layout is comprised of the following components: TextView, Button TextView and Button. Button. The basic configurations of LinearLayout How To Configure OSPF Multi Area On 4 Routers In Cisco Packet Tracer
4. Conclusion.
To begin using linearLayout using Android Studio, it's helpful to know the different layout classes. Each layout class comes with its own set or utilities and behavior. LinearLayout is one of the most widely used layout classes. LinearLayout is a layout that lays out its children horizontally and vertically. It has multiple children that it can support, which means you can use it to display an item list such as a navigation bar or even a navigation drawer. Another thing to consider about linearLayout is that it does not have any way to connect to its children, other than setsChild() as well as getChild(). This means that you will have to go through each child to access the desired one.
Basic Cisco Router Configuration | Cisco Packet Tracer Tutorial
0 notes
superaakash24 · 5 years ago
Link
0 notes
iyarpage · 7 years ago
Text
Android App Widgets Tutorial
The most successful applications are often the simplest to use. This means that users want to see the information they need “at-a-glance” without unlocking their phone or launching the related app. On the Android platform you can achieve this in two different ways. The first, and most recent, is Android Wear, and you can learn more about in Getting Started with Android Wear with Kotlin. The second, the topic of this tutorial, is through the implementation of App Widgets. App Widgets have been available in the Android ecosystem since version Android 1.6 (Donut).
In this tutorial you’ll create an App Widget for a Coffee Log application that will allow you to control your daily usage of caffeine right from your home screen. :]
Note: Most developers love coffee, but we also know that health is very important, so I advise you to read the interesting article Health and Fitness for Developers
You’ll follow the typical process for Widget development and learn how to:
Create the Widget user interface
Get up-to-date information in the Widget
Interact with the Widget
If you’re new to Android Development, I recommended that you read Beginning Android Development with Kotlin before you start, as well as Kotlin for Android. For this tutorial you’ll also need Android Studio 3.1.2 or later.
Getting started
The first thing you should do is to download the sample project for this tutorial using the download button at the top or bottom of the tutorial. The zip file contains Android Studio projects for the starter and final versions of the Coffee Log application.
Unzip the file in a folder of your choice, go to File/Open or choose “Open an existing Android Studio project” from the Welcome to Android Studio window, and select the build.gradle file in the root folder of the starter project.
Once the project finishes loading and performing a Gradle build, you can have a look at the file structure, which should be like this:
Now that you are in the project, take a look around, especially in MainActivity, where all the logging happens. CoffeeTypes is a simple enum class with all the coffee types and their caffeine quantity in grams, while the CoffeeLoggerPersistence class is managing persistence using SharedPreferences.
It’s time to start tracking our caffeine consumption! Build and run the app by going to the Build\Make Project or using the green “play” button from the toolbar. The app will appear in your emulator or device, looking like this:
The app allows you to see how many grams of coffee you drank so far today and select new drinks to update your consumption count. Each selection leads to an update of the total displayed.
To use the app to log your coffee consumption, you have to launch the full application. As always, we can do better. What about making your user’s life simpler with an App Widget like this one?
With a Widget, you can access the same information as the application, and display a powerful motivational quote, just by using your device home screen. As you can see the layout is different because the list is now a set of 3 buttons.
There’s a lot to cover to create an App Widegt, so let’s dig in!
App widget anatomy
As the Android documentation says, an App Widget is a component that can be embedded in other applications, typically the Home screen. Security and performance are very important, so the Android platform has defined a very clear protocol that describes how an App Widget communicates with its own app and interacts with the hosting one. This is why the developer has to provide a configuration file with the following information:
The Widget layout
The Widget screen space
Whether the Widget can resize and how
A preview image that users will see when dragging the Widget on the screen
How often refreshing data can happen
An optional Configuration screen
As you’ll see, the Android system uses this information in different stages of the Widget lifecycle. The layout information is useful when the Widget is running and interacting with the user. Resize, preview and screen space required are useful when the user decides to select the Widget and drag it into the Home screen.
User interface
As you’ve seen in the previous images, apps and Widgets have different UIs. This is because the available space is different, as well as the user interaction modes. For both apps and Widgets, you can define the layout using a resource file.
You have to remember that a Widget is running in a different application and so some restrictions are in place for security and performance reasons. This means that you can only use a subset of the standard components, with which you can then interact only using a specific object of type RemoteViews. In particular, you can use only:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
Along with ViewStub, which allows a lazy inflation of a layout, you can only use the following containers:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
Extensions of these classes are not allowed.
The check on these constraints is strong. Because of these restrinctions, a Widget layout has to be very simple and only use simple components like TextView, Button or ImageView.
Resizability and preview
The configuration file is the mechanism used to describe your Widget to the Android system. You can use this for setting the supported Widget sizes, telling the system whether the Widget is resizable or not, and providing an image to display when the user decides to add a Widget to their Home screen. You’ll see all of these when you insert your Widget for the first time.
Refreshing the widget
The data the Widget displays must always be up to date without wasting system resources. This means that the UI should be updated only when the data changes, and this can happen for different reasons. If the user interacts with the Widget, you need a way to update the UI and then send the event to the main app. If something is happening in the main app, you need a way to tell the Widget to refresh.
The Android platform also provides a third way, an automatic refresh of the Widget at an interval that can be set using the configuration file. Performance limitations don’t allow an update frequency greater than 30 minutes.
Widget customisation
In the case of Coffee Log, there are just three different type of coffees. But what if the user is not interested in Long coffee or they just want a different drink instead, or what if they want to simply change the quantity of grams. Or maybe the user wants to customise the background color of the Widget. As you’ll see, it’s possible to provide a configuration screen to allow all the needed customisation.
Create your Widget
Enough theory, now you can start creating your Widget. Creating a Widget requires the definition of some code and configuration files according to the specification defined by the Android platform.
Android Studio makes this process very easy, through the usage of a simple wizard, which you can access by selecting New\Widget\App widget from the File menu. You’ll see the following window:
Add the following input to the window:
Class name: CoffeeLoggerWidget
Minimum Width (cells): 3
Minimum Height (cells): 2
Here you can also see how it’s possible to define whether the Widget is resizable and what its possible destinations are. A Widget is usually part of the Home screen, but it could also part of the Keyguard, which is the screen that appears when the phone is locked.
Select Finish, and Android Studio will create three files for you:
CoffeeLoggerWidget.kt: this is a Kotlin class with the same name used in the wizard, and acts as the controller for the Widget. You’ll learn how to change this code in order to access the UI component through the RemoteViews class and how to receive and manage events from the Widget itself.
coffee_logger_widget_info.xml: this is the configuration file we described earlier with information about the refresh rate, resizability, dimensions, etc. This is the file you’re going to edit in order to provide a configuration Activity for the Widget.
coffee_logger_widget.xml: this file contains the widget’s user interface layout.
It’s important to note where all these files are in the project structure:
In particular, you see how the configuration file has been created as an XML resource file.
As you’ll see later, the wizard also made some changes to the app AndroidManifest.xml file.
Customizing the User Interface
In order to customize the UI for the Widget, open coffee_logger_widget.xml in the app\res\layout folder. The Android Studio wizard generated the following layout that you need to update:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#09C" android:padding="@dimen/widget_margin"> <TextView android:id="@+id/appwidget_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:layout_margin="8dp" android:background="#09C" android:contentDescription="@string/appwidget_text" android:text="@string/appwidget_text" android:textColor="#ffffff" android:textSize="24sp" android:textStyle="bold|italic" /> </RelativeLayout>
Remove the TextView and replace the RelativeLayout with a LinearLayout. In Android Studio, you can do this by double-clicking on the old name and typing the new name in its place. After this change you should have this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#09C" android:padding="@dimen/widget_margin"> </LinearLayout>
Note: You’re going to use styles that are already defined in the sample project. They contain text sizes and colors, heights, widths, alignments, and other style values. If you are curious about them, check out styles.xml in the res/values folder.
Next, add three more attributes to the LinearLayout:
... android:id="@+id/widget_layout" android:orientation="vertical" android:gravity="center" ...
The android:orientation and android:gravity attributes give the LinearLayout information about how to align its content. Providing an id is also important in case we need to get a reference to the layout in the Kotlin code.
To achieve rounded corners, change the android:background attribute to @drawable/background, a drawable available in the starter project. Now the root element of the layout looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widget_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/background" android:gravity="center" android:orientation="vertical" android:padding="@dimen/widget_margin"> </LinearLayout>
Thinking vertically
For the sake of aesthetics, the user interface should look good regardless of the Widget size. It’s best to have the Widget elements spread over the available space. There are many ways to achieve that, but you should go for the simplest which consists of adding some TextView components that will expand in the remaining space between the rest of the elements.
Here’s a schematic of the layout you’ll create:
The green pattern will be a TextView that expands vertically and the blue pattern will be a TextView that expands horizontally. Keep this schematic in mind as you build the layout to understand why you add each element.
Note:If you’re tempted to fill the empty spaces using a Space instead of TextView, remember that a Widget has some UI restrictions and that a Space is not one of the allowed components.
The first element in the LinearLayout is a vertical space that you can define by adding this code as the first child:
<TextView style="@style/WidgetButtonVerticalSpace" />
Now you can add the TextView components for the amout of coffee:
<TextView android:id="@+id/appwidget_text" style="@style/WidgetTextView.Big" /> <TextView style="@style/WidgetTextView" android:text="@string/grams" />
Then add another TextView for the next vertical space before the buttons:
<TextView style="@style/WidgetButtonVerticalSpace" />
Notice that the first text view needs to have an id because you will need to change the text later on from the Kotlin code. The second one is fixed text. You’re using the predefined styles on the text views.
Next, add a container for the buttons as a LinearLayout with horizontal orientation:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <!-- Buttons go here --> </LinearLayout>
Then a TextView for the quote after the last vertical space.
<TextView style="@style/WidgetButtonVerticalSpace" /> <TextView android:id="@+id/coffee_quote" style="@style/WidgetQuote" />
Adding buttons
Now the green part of the layout is fnished and you have to deal with the blue part for the buttons following this schematic:
You’ve already created a container for them so you just need to start with a TextView that expands horizontally and will keep the first button at a distance from the left margin:
<TextView style="@style/WidgetButtonHorizontalSpace" />
Then you can add the first button for smallest coffee in the world:
<LinearLayout android:id="@+id/ristretto_button" style="@style/WidgetBeverageButton" > <ImageView style="@style/WidgetButtonImage" android:src="@drawable/ic_ristretto" /> <TextView style="@style/WidgetButtonText" android:text="@string/ristretto_short" /> </LinearLayout> <TextView style="@style/WidgetButtonHorizontalSpace" />
Each button has a LinearLayout that contains an ImageView and a TextView. After the button, you added another horizontally expanding TextView to help the buttons spread.
Add the next button for Espresso:
<LinearLayout android:id="@+id/espresso_button" style="@style/WidgetBeverageButton"> <ImageView style="@style/WidgetButtonImage" android:src="@drawable/ic_espresso" /> <TextView style="@style/WidgetButtonText" android:text="@string/espresso_short" /> </LinearLayout> <TextView style="@style/WidgetButtonHorizontalSpace" />
And the final button for the Long:
<LinearLayout android:id="@+id/long_button" style="@style/WidgetBeverageButton" > <ImageView style="@style/WidgetButtonImage" android:src="@drawable/ic_long_coffee" /> <TextView style="@style/WidgetButtonText" android:text="@string/long_coffee_short" /> </LinearLayout> <TextView style="@style/WidgetButtonHorizontalSpace" />
Phew! That was long but you’re done with the layout for the widget. :]
Run your Widget
The Widget you’ve created is beautiful, but it’s not doing anything quite yet. Build and run your app to make sure there’s no error in the XML. Just to be sure everything is fine, add the widget to the screen. If you’ve never added a widget to your Home screen before, here are the steps:
Go to the Home screen
Long press on an empty space
Select “Widgets”
Long press on the Coffee Log Widget
Drop it wherever you like on the screen
Your widget looks like this:
Notice how the autogenerated code populated the first TextView with “EXAMPLE”. Later in this tutorial, you will update it with the right number of coffee grams.
Performing actions
Now it’s time to add some interactivity to the Widget. When the user selects a button, you’ll have to open MainActivity, passing information about the selected coffee in order to update the total number of grams in today’s record.
Unfortunately, launching a simple Intent is not enough, because we have to remember that our Widget is running in an application that is different from ours and runs in another Android process. The Android platform has a solution for this called PendingIntent that is basically a way to ask another application to launch an Intent for you.
Open then the CoffeeLoggerWidget.kt file and add this utility function at the end of the companion object:
private fun getPendingIntent(context: Context, value: Int): PendingIntent { //1 val intent = Intent(context, MainActivity::class.java) //2 intent.action = Constants.ADD_COFFEE_INTENT //3 intent.putExtra(Constants.GRAMS_EXTRA, value) //4 return PendingIntent.getActivity(context, value, intent, 0) }
This Kotlin function has the responsibility of creating a PendingIntent for a given coffee:
First you define the Intent to launch as usual using the destination class as argument; in your case it’s the MainActivity class.
The MainActivity can be launched in different ways, and you need something that identifies how much to vary the coffee content. To do this you use an action MainActivity can recognise.
You also need to put into the Intent the quantity to add. Remember, MainActivity doesn’t know what button was pressed on the Widget!
Create the PendingIntent and return it to the caller of the function
Since you now have the action prepared, attach them to the buttons. Go to the updateAppWidget() function in the companion object and add the following code just before its last instruction appWidgetManager.updateAppWidget(...):
views.setOnClickPendingIntent(R.id.ristretto_button, getPendingIntent(context, CoffeeTypes.RISTRETTO.grams)) views.setOnClickPendingIntent(R.id.espresso_button, getPendingIntent(context, CoffeeTypes.ESPRESSO.grams)) views.setOnClickPendingIntent(R.id.long_button, getPendingIntent(context, CoffeeTypes.LONG.grams))
It is worth noting that updateAppWidget() is a convenience method the Android Studio wizard created in order to encapsulate the update logic for a given Widget. Looking at the same Kotlin class, you see that it’s invoked in the onUpdate() method for each Widget that requires an update. This call also happens when the Widget appears in the hosting application for the first time.
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { // There may be multiple widgets active, so update all of them for (appWidgetId in appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId) } }
The RemoteViews class
Now your code should look like this:
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { //1 val widgetText = context.getString(R.string.appwidget_text) //2 val views = RemoteViews(context.packageName, R.layout.coffee_logger_widget) //3 views.setTextViewText(R.id.appwidget_text, widgetText) //4 views.setOnClickPendingIntent(R.id.ristretto_button, getPendingIntent(context, CoffeeTypes.RISTRETTO.grams)) views.setOnClickPendingIntent(R.id.espresso_button, getPendingIntent(context, CoffeeTypes.ESPRESSO.grams)) views.setOnClickPendingIntent(R.id.long_button, getPendingIntent(context, CoffeeTypes.LONG.grams)) // 5 appWidgetManager.updateAppWidget(appWidgetId, views) }
Here’s what’s going on:
You’re using the Context in order to access a string resource.
An instance of the RemoteViews class is created and given the widget’s layout id. A RemoteViews is basically a mirror image of what you’re going to display in the Widget.
You set the previous string as content of the TextView with id R.id.appwidget_text. It’s very important to note that you can’t access the TextView directly and that only some operations are allowed using the RemoteViews; in this case you’re setting a text.
Using the RemoteViews instance, you register a PendingIntent to use when the user clicks on a each Widget button.
The last instruction binds the specific instance of RemoteViews to the specific instance of the Widget.
Build and run now. You won’t see any difference in the widget, but clicking the Widget buttons will open the app with an updated value of grams. Great job!
Updating the Widget
Widgets should always display the lastest available information, and the update frequency depends on the specific type of data. A Weather Widget doesn’t need a very frequent update, unlike the score of a football match or the price of a specific stock.
You need a way to invoke the previous onUpdate() method at a specific time interval in order to create the new RemoteViews with the new data.
The following drawing gives you an idea of the process:
The problem is how to send the “I need a refresh!” message to the Widget.
Widget configuration
When the update frequency you need is longer than 30 minutes, you don’t need to write any code and you can simply rely on the configuration file coffee_logger_widget_info.xml Android Studio generated in the res\xml folder.
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/coffee_logger_widget" android:initialLayout="@layout/coffee_logger_widget" android:minHeight="110dp" android:minWidth="180dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="86400000" android:widgetCategory="home_screen"> </appwidget-provider>
The Widget refresh rate is the one defined in the attribute android:updatePeriodMillis. The default value is one day in milliseconds.
Managing updates requests
If you understand how the Android platform manages updates to your Widget, you can replicate the same thing at will. The Android Studio wizard created the CoffeeLoggerWidget class that extends AppWidgetProvider, but we didn’t realize that this was a particular implementation of a BroadcastReceiver.
You can see that by looking at the updates the wizard made to the AndroidManifest.xml file:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.raywenderlich.android.coffeelogs"> - - - - <receiver android:name=".CoffeeLoggerWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/coffee_logger_widget_info" /> </receiver> - - - - </manifest>
Based on the specific Intent‘s action, the AppWidgetProvider dispatches the call to a different methods. Launching an Intent with the android.appwidget.action.APPWIDGET_UPDATE action results in the invocation of the onUpdate() function.
This is exactly what the Android system does at the interval set in the coffee_logger_widget_info.xml configuration file. This means that the updateAppWidget() function is the perfect place for the code to execute on every update.
So add the following line to the beginning of the function:
val coffeeLoggerPersistence = CoffeeLoggerPersistence(context)
and change widgetText to take the value from there:
val widgetText = coffeeLoggerPersistence.loadTitlePref().toString()
Good! Build and run and you’ll see that the widget is periodically updating the “grams” value. Seems like someone had a little too much coffee:
Update the widget manually
If your app needs to update the data in the Widget more frequently, you already have the solution: you can simply periodically launch the same Intent the Android system does. In the case of the Coffee Log application this happens every time the user selects a coffee in the app.
Open MainActivity and add the following code at the end of refreshTodayLabel:
// Send a broadcast so that the Operating system updates the widget // 1 val man = AppWidgetManager.getInstance(this) // 2 val ids = man.getAppWidgetIds( ComponentName(this, CoffeeLoggerWidget::class.java)) // 3 val updateIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE) // 4 updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) // 5 sendBroadcast(updateIntent)
Since this code has some new elements, let me walk you through it:
Get the AppWidgetManager instance, which is responsible for all the installed Widgets.
Ask for the identifiers of all the instances of your widget (you could add more than one to your homescreen).
Create an Intent with the android.appwidget.action.APPWIDGET_UPDATE action asking for an update.
Add the ids of the widgets you are sending the Intent to as extras of the Intent for the AppWidgetManager.EXTRA_APPWIDGET_IDS key.
Finally, send the broadcast message.
Build and run tha app to check that everytime you add some coffee, the widget also updates.
Communicating via Service
Not all the updates needed for Widgets are a consequence of an action from the user. Typical cases are data from a server through periodic polling and push notification events. In cases like these, the request has to come from a different component, which you usually implement as an Android Service.
Choose File\New\Service\Service and change the name to CoffeeQuotesService.
When you click Finish, Android studio generates a Kotlin file for you for the Service.
In CoffeeQuotesService, replace the current implementation of onBind() with:
return null
Change the return type of onBind to be the nullable IBinder?.
Then add this function, which is the one the Android system invokes at every launch of the service Service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val appWidgetManager = AppWidgetManager.getInstance(this) val allWidgetIds = intent?.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS) //1 if (allWidgetIds != null) { //2 for (appWidgetId in allWidgetIds) { //3 CoffeeLoggerWidget.updateAppWidget(this, appWidgetManager, appWidgetId) } } return super.onStartCommand(intent, flags, startId) }
You’ve seen the first two lines before. The others do the following:
Check that the array of allWidgetIds was in the Intent.
Loop through the allWidgetIds list.
Update each widget.
Now, you need to call this service instead of directly updating the widget. Open CoffeeLoggerWidget and replace the content of onUpdate() with the following in order to start the Service:
val intent = Intent(context.applicationContext, CoffeeQuotesService::class.java) intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) context.startService(intent)
This creates an Intent, puts the Widget ids in the intent, and starts the Service.
In the companion object, add the following function:
private fun getRandomQuote(context: Context): String { //1 val quotes = context.resources.getStringArray(R.array.coffee_texts) //2 val rand = Math.random() * quotes.size //3 return quotes[rand.toInt()].toString() }
This function generates a random coffee quote:
It takes a quote array from the strings file
It picks a random number
Finally, it returns the string at the random position
After you have the string, update the widget. In updateAppWidget() add this before the last call:
views.setTextViewText(R.id.coffee_quote, getRandomQuote(context))
That’s it. Every time the widget updates, you get a new quote!
Making it personal
People like to personalize the look and functionality of their Home screens, and Widgets are no exception. You have to take into account that a general purpose Widget won’t bring much value to a user. To make it personal you need to let the users set up preferences and configurations.
Earlier, when covering the configuration of a Widget, you learned that it can have a Configuration screen. This is an Activity that is automatically launched when the user adds a Widget on the home screen. Note that the preferences are set up per Widget because users can add more than one instance of a Widget. It’s better to think about saving this preferences with the id of the Widget.
In this project, the configuration screen could contain a coffee amount limit. If the user logs more coffee than the limit, the Widget will turn into a soft but alarming pink.
Creating a preferences screen
The preference screen for a Widget is an Activity. Choose New\Activity\Empty activity from the File menu and edit the fields to be
Activity name: CoffeeLoggerWidgetConfigureActivity
Layout Name: activity_coffee_logger_widget_configure
Make sure the Launcher Activity checkbox is unchecked and the Source Language is Kotlin.
When you click Finish, Android Studio will generate the code for the new Activity and a template for the layout file, along with adding the registration of the Activity in the AndroidManifest.xml file.
Now create the layout for the configuration screen. Open activity_coffee_logger_widget_configure.xml and add the following:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:labelFor="@+id/appwidget_text" android:text="@string/coffee_amount_limit" /> <EditText android:id="@id/appwidget_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="number" /> <Button android:id="@+id/add_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/save_configuration" /> </LinearLayout>
The layout is nothing complicated: a TextView that represents a label to the EditText, and a Button for the user to save the preferences.
Know your limits
Open CoffeeLoggerWidgetConfigureActivity and add these fields above onCreate() (developers usually put fields at the beginning of the class):
private lateinit var appWidgetText: EditText private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID private val coffeeLoggerPersistence = CoffeeLoggerPersistence(this)
You will need to use these fields later to save the limit value for each widget.
In onCreate(), add the following code at the end:
//1 appWidgetText = findViewById(R.id.appwidget_text) //2 val extras = intent.extras //3 appWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) //4 setResult(Activity.RESULT_CANCELED)
Here’s what the code does:
Find the EditText in the layout.
Get the extras from the Intent that launched the Activity.
Extract the appWidgetId of the widget.
Make sure that if the user doesn’t press the “Save Configuration” button, the widget is not added.
Finally, you need to save the configuration when the user presses the “Save Configuration” button. Below onCreate(), declare the following OnClickListener implementation:
private var onClickListener: View.OnClickListener = View.OnClickListener { // 1 val widgetText = appWidgetText.text.toString() // 2 coffeeLoggerPersistence.saveLimitPref(widgetText.toInt(), appWidgetId) // 3 val resultValue = Intent() resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) // 4 setResult(RESULT_OK, resultValue) // 5 finish() }
Here you:
Get the text input – the coffee limit.
Save the limit to local storage (using the Widget id).
Create a new Intent to return to the caller of the Activity and add the id of the Widget you’re configuring.
Tell the operating system that the configuration is OK. Do this by passing an Intent that contains the widget id.
Close the configuration screen
Attach this listener to the button by adding the following line below setContentView() in onCreate():
findViewById<View>(R.id.add_button).setOnClickListener(onClickListener)
This is a chained instruction that finds the Button object and sets its listener.
Linking preferences to the widget
It is a good idea to refresh the widget after the user saves the preferences. That’s because the limit might already be exceeded at the moment of adding a new widget. For this reason, write another method at the end of CoffeeLoggerWidgetConfigureActivity to trigger the refresh:
private fun updateWidget() { val appWidgetManager = AppWidgetManager.getInstance(this) CoffeeLoggerWidget.updateAppWidget(this, appWidgetManager, appWidgetId) }
The function retrieves the AppWidgetManager and triggers an update to the corresponding widget. Call this function in the OnClickListener after saving the coffee limit to coffeeLoggerPersistence. It should be before creating the Intent:
updateWidget()
To launch the configuration screen whenever the user adds a widget, you need to add it to the widget configuration file. With this in mind, open coffee_logger_widget_info.xml and add the following attribute to appwidget-provider:
android:configure="com.raywenderlich.android.coffeelogs.CoffeeLoggerWidgetConfigureActivity"
Build and run, then go to the home screen. Long press the widget and drag it to the “Remove” area. Add another widget as before and check that the configuration screen appears. It should look like this:
Enter a value in the field like 10 and press “Save configuration” to add the widget.
To make the widget react to the limit, add this in CoffeeLoggerWidget inside updateAppWidget*(, before the last line:
// 1 val limit = coffeeLoggerPersistence.getLimitPref(appWidgetId) // 2 val background = if (limit <= widgetText.toInt()) R.drawable.background_overlimit else R.drawable.background // 3 views.setInt(R.id.widget_layout, "setBackgroundResource", background)
Step by step:
First, get the limit saved by the user for that widget.
Decide if the user exceeds the limit of coffee and establish one of the two possible backgrounds: pink or blue.
Set the background to the widget's root element.
Finally, build and run. After the app opens log more coffees than the limit you set. Let's say your limit was 10: log three Espresso and go back to the home screen. As a result, your widget is now pink:
Best practices
Some final advice before you start adventuring into the world of Widgets:
Design the smallest Widget size you can. Don't take up screen real-estate if you don't need it. Be aware that the user might resize it into a bigger area.
Don't refresh the Widget too often because it will drain the battery. On the other hand, don't refresh it too rarely because it won't be useful on the screen.
Make sure you read the official guidelines for Widget design and follow the recommendations. Revisit them from time to time because things change and things get added.
Think of Widgets as a shortcut window into your app. Provide the most important information and actions in it.
Where to go from here
Congratulations, you've finished your App Widget! Download the final project using the button at the top or bottom of the tutorial.
You learned how to develop an App widget to track your coffee intake. In summary, some of your new skills are:
Create a widget layout
Link a configuration screen
Communicate via a Service
... and tie them all together. This is impressive!
You can learn more about App Widgets by checking out the official docs.
For a better understanding of Intents, have a look at the Android Intents Tutorial.
You can create a better user interface for your apps and widgets with more Material Design. Get a little knowledge boost from Android: An Introduction to Material Design.
If you have any questions or comments about Android App Widgets, please join the forum discussion below!
The post Android App Widgets Tutorial appeared first on Ray Wenderlich.
Android App Widgets Tutorial published first on https://medium.com/@koresol
0 notes
mobilenamic · 7 years ago
Text
How to Access REST Services with Qt and V-Play: Weather Service Example App (Open Source)
REST and RESTful web services are the most common way to access data through the Internet. Qt with V-Play provides an easy way to connect via REST. This article guides you through the most important steps to create an App and connect to a REST service. Additionally, it provides reusable code snippets.
Spoiler: Basic REST Example with V-Play
Before we jump into the details of creating the sample App, this is how a basic request to a REST service looks, using the XMLHttpRequest:
// Create the XMLHttpRequest object var xhr = new XMLHttpRequest // Listen to the readyStateChanged signal xhr.onreadystatechange = function() { // If the state changed to DONE, we can parse the response if (xhr.readyState === XMLHttpRequest.DONE) { // The responseText looks like this {"ip":"xxx.xxx.xxx.xxx"} // Parse the responseText string to JSON format var responseJSON = JSON.parse(xhr.responseText) // Read the ip property of the response var ip = responseJSON.ip // Log your ip to the console output console.debug("My IP is: " + ip) } } // Define the target of your request xhr.open("GET", "https://api.ipify.org?format=json") // Execute the request xhr.send()
Real-Life Sample Project
For the most useful results, we build a real-life Qt client. It accesses one of the web’s most popular weather services. It’s easy to adapt to any other REST service: the process is always the same. You only need to change the endpoint URL and parse the corresponding content.
The full sample is available open-source on GitHub:
Download GitHub Example
  Architecture
The app consists of two files:
Main.qml: contains the UI and REST logic
DataModel.qml: stores & caches the parsed data from the REST service
Basic UI for a Responsive REST Client App
First, we create the user interface: the QML items in “Main.qml”. We use V-Play APIs. They adapt to the style of the target platform (Desktop, Android, iOS). These three items are usually present in every Qt / QML app:
The App component is always the top-level element in the QML file. It adds a lot of vital layout data to standard Qt classes. Two mechanisms are especially important. Device-independent pixels (dp) for sizes and scale-independent pixels (sp) for fonts.
Initially, our REST client app only has a single page. It’s still a good idea to add the NavigationStack item as a child. Later, it could handle navigating to a detail page. In our current app, the NavigationStack ensures that the top navigation bar is visible.
The third item is the Page. It’s the container for the automatic title bar and the visible QML items of our app. The platform-specific theming is applied automatically.
Visualization UI
After the generic UI elements, we define the custom interface. The user enters the city name in a SearchBar. The AppText elements below show the parsed data / error message.
QML Page Layout
We need vertically stacked UI elements. The generic QML ColumnLayout is the best layout manager for this scenario. Its documentation is short. Essentially, it’s a convenience version of the GridLayout with a single column.
QML contains another class which looks similar at first sight: the Column. What’s the difference of ColumnLayout vs Column?
Column is a Positioner. It arranges QML items in a regular fashion (i.e., below each other). The child items are responsible for their size. The Column takes care of the position.
ColumnLayout is a Layout. In addition to the position, it also manages the child item size. This makes it more suitable for responsive user interfaces. It ensures the individual item’s place on the screen is a good compromise of the available space and the minimum item size.
We’re dealing with a mobile UI. Thus, the ColumnLayout is the better choice.
ColumnLayout { anchors.fill: parent anchors.margins: app.dp(16) // ... QML child items ... }
To achieve the expected look & feel of the UI on a phone, we set two properties:
anchors.fill: parent – this stretches the layout to the available width and height. What happens if we don’t specify the layout size? The layout area would adapt to the minimum requested size of its managed QML items. The text items always grab the space they need for showing their contents. But the SearchBar is flexible: it takes what it gets and doesn’t have a minimum width. Therefore, the SearchBar would be too small or even invisible if we didn’t have enough text in the rest of the UI.
anchors.margins: app.dp(16) – text shouldn’t stick to the screen edge. The Google Material Design recommends screen edge left and right margins of 16dp. Note that in iOS, you should also consider the safe area in addition to the margins. This ensures your content is not within the cutout-areas of the iPhone X in landscape mode. In our app, we use a margin of 16 density-independent pixels. This ensures a similar spacing on all mobile phones, independent of the screen’s pixel density.
Data Input: SearchBar
Entering data for searching or filtering is a common task in every mobile app. Fortunately, V-Play includes an advanced QML item. It handles interaction and platform-specific theming. You get a full-blown search text input control by specifying a handful of settings. This screenshot shows an empty SearchBar with the iOS theme:
SearchBar { id: weatherSearchBar focus: true Layout.fillWidth: true placeHolderText: qsTr("Enter city name") onAccepted: loadJsonData() }
By setting the width to correspond to the parent’s width (-> the layout), we ensure the search bar always fills the available screen width. The placeHolderText is great for better usability.
Finally, with onAccepted, we call a custom JavaScript function that we will code shortly. This signal is emitted whenever the user presses the Return or Enter key.
Data Display: AppText
The AppText QML type is another component of V-Play. It is a styled QML Text item. It picks up the platform’s colors and styling by default.
Our layout consists of several AppText items. Each has a dynamic text property, which is bound to weather data from the DataModel. The layout ensures the items are below each other.
AppText { text: qsTr("Weather for %1").arg(DataModel.weatherData.weatherForCity) Layout.fillWidth: true wrapMode: Text.WordWrap color: Theme.tintColor font.family: Theme.boldFont.name font.bold: true font.weight: Font.Bold visible: DataModel.weatherAvailable }
Let’s examine the most complex AppText item in our layout: the header of the weather information. It shows the city name and country.
Text: the city name is bound to our data model. To prepare for future app translation, we wrap the text string with qsTr(). Read more: How to Make a Multi Language App or Game with V-Play
Layout: some text might not fit in a single line. Therefore, we activate word wrapping. The QML item needs to have a width to know where to insert the line break. We set the width to fill the available space provided by the layout.
Styling: we apply a bold and colored style to the text. The global Theme item of V-Play provides app-wide and platform-adapted color and font resources.
Visibility: the item should only be visible if weather data is available in the model. The binding automatically adapts the visibility.
Remaining Screen Height
By default, the layout distributes the available screen height between all items. Our current layout is quite compact. We don’t want a huge unused area between each text line.
To solve this, we add an Item with an activated fillHeight property. It’s not visible on the screen but is as tall as possible. This pushes the other items together.
Item { Layout.fillHeight: true }
Weather Services
The most popular weather services are OpenWeatherMap, Yahoo Weather and Weather Underground. In this tutorial, we use OpenWeatherMap. It’s quick to sign up, it provides free weather data access and is frequently used (also by Google).
API Key
Sign up for free at https://openweathermap.org/appid
Immediately afterwards, you get an email with usage samples. OpenWeatherMap already generated an API key called “Default” in your account.
It takes around 10 minutes until the key is active. Click on the sample link in your welcome email to check if the service is already up and running for your account.
REST Request & JSON Parsing with Qt / QML
How do we fill the UI with actual weather data? Most Internet services provide a REST API. In Qt, QML and JavaScript are tightly integrated. Instead of having to resort to C++ code, we can use standard JavaScript code to access a RESTful API.
In technical terms, the XMLHttpRequest object is embedded in the QML Global Object. Its functionality corresponds to the W3C Standard. The only exception is that it doesn’t enforce the same-origin policy. This makes our life easier, as we do not have to deal with CORS (Cross-Origin Resource Sharing). In QML JavaScript, all REST requests typically go to an external web server.
You need 4 steps for a REST request:
Instantiate XMLHttpRequest
Register a state change listener
Set the target URL & request properties
Send the REST request
Let’s analyze these steps:
1. Instantiate XMLHttpRequest
In the first line, we create an instance of the XMLHttpRequest object. It’s OK to be a local object. Every REST request creates a new instance.
var xhr = new XMLHttpRequest
2. Register a State Change Listener
By default, XMLHttpRequest is asynchronous. Therefore, we define an event handler. The most flexible approach is attaching a handler function to the onreadystatechange event.
During the lifetime of the REST request, it runs through several states:
UNSENT
OPENED
HEADERS_RECEIVED
LOADING
DONE <- request is finished. Is always called (also in case of an error!)
To provide progress indicators while loading, you could register for intermediate events. For quick requests like ours to a weather service, it’s usually enough to only handle the DONE event.
xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { // ... handle response data ... } }
3. Set the Target URL & Request Properties
Next, we configure the REST request. The open() function needs at least two parameters:
Method: the HTTP method for the REST request. The REST protocol allows full interaction with a server. To retrieve data, you use GET or POST methods (depending on the service definition). An interactive RESTful web service can also implement the PUT, PATCH and DELETE methods for modifying data.
Url: target to invoke. In case of a GET request, the parameters are part of the URL.
The open() function checks the data you supply and advances the request state to OPENED.
The OpenWeatherMap service works using GET requests. The URL needs to contain:
Query: the city name, e.g., ?q=Vienna We retrieve the query string from the text of the weatherSearchBar QML item.
Units: metric or imperial, e.g., &units=metric
API key / App ID: sign up to get a free app id. E.g., &appid=xyz It’s a good idea to store your API key / App ID as a property in your app.
The full code to construct the query URL and to open the REST request:
var params = "q=" + weatherSearchBar.text + "&units=metric&appid=" + app.weatherServiceAppId xhr.open("GET", "http://api.openweathermap.org/data/2.5/weather?" + params)
4. Send the REST Request
A simple statement. If your RESTful service uses POST, you supply the body as parameter. For our GET method, call:
xhr.send()
Parse the REST Response
Once our REST request proceeds to the DONE state, we check the results. As we call an external service through the Internet, many things can go wrong. It’s important to handle all possible failures.
Most REST / JSON tutorials show a simplified handler. It’d fail without an Internet connection. Here, we handle all possible issues.
JSON Response
If everything went well, we received a response. The responseText property contains the JSON data, which we parse through JSON.parse(). This is the shortened JSON response. The full OpenWeatherMap response contains more data.
{ "weather": [ { "main": "Snow", "description": "light snow", "icon": "13n" } ], "main": { "temp": -0.32, }, "name": "Vienna", "cod": 200 }
Identify Failed REST Requests
A request can fail because of several reasons:
Connection issues: e.g., no Internet connection or the server is down
Request issues: the server / service does not understand your request, e.g., due to a typo in the URL
Access issues: the RESTful service can’t fulfil your request. Examples: unauthorized client, invalid API key
Service issues: unable to send a positive response. E.g., because the requested city doesn’t exist, or because no weather is currently available for the city
Usually, REST services only send the positive HTTP status code 200 if everything worked well. In all other cases, they send a different HTTP status code (e.g., 404 if the city wasn’t found). Additionally, it may send a response text even in case of a failure. This provides extra information on what went wrong.
But, some REST services always return the successful HTTP code 200. They only report an error in the response text. Check the service documentation and test the REST APIs.
OpenWeatherMap is a well-designed RESTful service. It always sends a response JSON. The JSON data includes a response code (called “cod”). So, it’s best to parse the responseText (if available). If parsing the JSON was successful (!= null) and the JSON contains a “cod” of 200, we know that everything went well. Otherwise, we need to analyze the error.
var parsedWeather = xhr.responseText ? JSON.parse(xhr.responseText) : null if (parsedWeather && parsedWeather.cod === 200) { // Success: received city weather data } else { // Issue with the REST request }
Successful REST Request
If the REST service returned data, we reset any previous error message in our UI. Then, we update our DataModel.
// Success: received city weather data app.errorMsg = "" DataModel.updateFromJson(parsedWeather)
REST Response Error Handling
In technical terms, we differentiate 3 types of REST request failures:
The status code of the XMLHttpRequest is still 0, even though its status already changed to DONE. This indicates that the request didn’t go through. Potential reasons: no Internet connection, server is down, …
We received response text, but it contains an error description. For our weather app, we show the message to the user. Potential reasons: city not found, API key is wrong, …
No response text, but a HTTP response status code. Create a custom error message for the user. Potential reasons: REST service crashed (e.g., 500 Internal Server Error), …
This code snippet handles all 3 cases. It formulates a brief error message for the app user.
// Issue with the REST request if (xhr.status === 0) { // The request didn't go through, e.g., no Internet connection or the server is down app.errorMsg = "Unable to send weather request" } else if (parsedWeather && parsedWeather.message) { // Received a response, but the server reported the request was not successful app.errorMsg = parsedWeather.message } else { // All other cases - print the HTTP response status code / message app.errorMsg = "Request error: " + xhr.status + " / " + xhr.statusText }
JSON Data Model for REST Requests
An architecture that separates the model from the view makes your app easy to extend. So, we create an extra class to manage the data model.
Right-click the qml folder in Qt Creator’s “Project”-window and select “Add new…”. Choose the Item template in the V-Play Apps category. Call the file “DataModel.qml”.
Singleton Pattern in QML
We want our model to be accessible in the whole app. Thus, we use the Singleton pattern. This requires three steps:
1. Prepare the QML file
In the very first line of DataModel.qml – before the import statements – add:
pragma Singleton
2. Register the singleton
Create a new file called “qmldir” in Other files > qml in the Projects window.
Add the following line to the qmldir file:
singleton DataModel 1.0 DataModel.qml
In the next step, we’ll import a directory to access our singleton file. When importing a directory, Qt always looks first for a qmldir file. It’s using that to customize the way it sees and imports qml files. In our case, the QML engine now knows to treat the DataModel.qml file as a singleton.
3. Import the Singletons
In Main.qml, add the following import after all the other import statements:
import "."
This causes Qt to scan the directory. From now on, our DataModel.qml file is accessible via the “DataModel” identifier in Main.qml.
QML Data Model Structure
Our data model requires three properties. Two contain information about the state of the data (weatherAvailable and weatherFromCache).
The third property is weatherData. It’s defined with the type var and initialized as an array with []. This allows assigning key-value pairs for the actual data. It lets us cache and restore the data with a single statement. Dynamic binding to the UI is still possible.
The basic structure of our data model:
pragma Singleton import VPlay 2.0 import QtQuick 2.7 Item { id: dataModel property bool weatherAvailable: false property bool weatherFromCache: false property var weatherData: [] }
Save Weather Data to the Model
We want to keep the model generic. You could add a different data provider later, or the JSON layout changes.
Our app extracts the data it needs from the weather JSON. The app then stores the relevant data in its own model. If we’d later migrate to a different data provider, it wouldn’t influence the model or the UI.
The most efficient way to achieve this: create a setModelData() function. It takes the parsed parameters and saves it to our weatherData property.
function setModelData(weatherAvailable, weatherForCity, weatherDate, weatherTemp, weatherCondition, weatherIconUrl, weatherFromCache) { dataModel.weatherData = { 'weatherForCity': weatherForCity, 'weatherDate': weatherDate, 'weatherTemp': weatherTemp, 'weatherCondition': weatherCondition, 'weatherIconUrl': weatherIconUrl } dataModel.weatherAvailable = weatherAvailable dataModel.weatherFromCache = weatherFromCache }
Parse JSON Data from the Weather Service
In a previous step, we already converted the JSON text to objects with JSON.parse(xhr.responseText).
The QML runtime implements the ECMAScript language specification. Thus, working with JSON data in QML is like standard JavaScript. Every JSON object is accessible as a property. You retrieve JSON array values using the [] accessor.
The updateFromJson() method extracts the useful information from JSON and forwards it to our model.
function updateFromJson(parsedWeatherJson) { // Use the new parsed JSON file to update the model and the cache setModelData(true, parsedWeatherJson.name + ", " + parsedWeatherJson.sys.country, new Date(), parsedWeatherJson.main.temp, parsedWeatherJson.weather[0].main, "http://openweathermap.org/img/w/" + parsedWeatherJson.weather[0].icon + ".png", false) }
Note: JavaScript and QML are very similar. The differences are hard to spot.
date.now() generates a JavaScript date object.
new Date() instantiates a Qt Date object.
We choose the Qt variant, as it makes serialization easier. Qt provides platform-independent storage and transmission of all Qt data types.
Now, the app is functional! Go ahead and test it on any platform or device.
Data Persistence
REST requests over the Internet take time. To improve the user experience, we add data persistence to our app. With this enhancement, our app immediately shows cached data when it’s started. The update request to the webservice then runs in the background.
We stored our weather data in DataModel.qml. Now, we extend our model to encapsulate its own caching.
Storage QML Type
File handling is different on every platform. V-Play includes a powerful cross-platform type called Storage. It handles the most common use-case: key-value data storage. You don’t need to write complex SQL statements like in the base Qt Quick Local Storage QML type.
At the same time, all built-in QML types are serializable to Strings. With a single line of code, we export the whole model to persistent storage.
Initialize the Storage by adding the element as a child of our dataModel item:
Storage { id: weatherLocalStorage Component.onCompleted: { // After the storage has been initialized, check if any weather data is cached. // If yes, load it into our model. loadModelFromStorage() } }
Load QML Data from Persistent Storage
The ready-made implementation from V-Play calls onCompleted() once the storage is accessible. We use this to check if cached data is available. We access stored data using getValue(). If no data is available, it returns undefined. A simple if(savedWeatherData) statement lets us execute code accordingly.
function loadModelFromStorage() { var savedWeatherData = weatherLocalStorage.getValue("weatherData") if (savedWeatherData) { dataModel.weatherData = savedWeatherData dataModel.weatherAvailable = true dataModel.weatherFromCache = true } }
The powerful APIs de-serialize the storage into the live model data. To inform the user that he’s now seeing cached data, we set the other model properties accordingly.
Save QML Data to Persistent Storage
To serialize our weather data to the storage, we use setValue(). The second argument is our whole weather data storage object. V-Play automatically serializes it to the storage.
function saveModelToStorage() { weatherLocalStorage.setValue("weatherData", dataModel.weatherData) }
What’s the best place to update the storage? Especially in mobile apps, it’s recommended to immediately update persistent storage. A mobile operating system may shut down apps at any time, e.g., for an incoming call and low system resources. Thus, call saveModelToStorage() at the end of our setModelData() method.
Deploy the App to Android, iOS and Desktop
V-Play provides platform-independent styling and a responsive layout. The V-Play Live Service features live code reloading every time you save changes to QML files. The PC client simulates the app appearance on iOS, Android or Desktop platforms.
With the V-Play Live Scripting App for Android or iOS, you can extend testing to a real mobile device. Your development PC and the phone communicate over the local area network.
This screenshot shows a sample configuration of the V-Play Live Server. Two clients are connected. The app is running on a Google Pixel 2 phone (with the V-Play Live App) and on a desktop client.
  Download the final, open source QML REST client sample code from GitHub:
Download GitHub Example
It extends the code from this article with several comments. It’s a great starting point for your own REST projects!
    More Posts Like This
youtube
Tumblr media
The post How to Access REST Services with Qt and V-Play: Weather Service Example App (Open Source) appeared first on V-Play Engine.
How to Access REST Services with Qt and V-Play: Weather Service Example App (Open Source) published first on https://medium.com/@TheTruthSpy
0 notes
luxus4me · 7 years ago
Link
Envato Tuts+ Code http://j.mp/2uEwfJz
NativeScript is a framework for building cross-platform native mobile apps using XML, CSS, and JavaScript. In this series, we're trying out some of the cool things you can do with a NativeScript app: geolocation and Google Maps integration, SQLite database, Firebase integration, and push notifications. Along the way, we're building a fitness app with real-time capabilities that will use each of these features.
In this tutorial, you'll learn how to integrate a SQLite database into the app to store data locally. Specifically, we'll be storing the walking sessions data that we gathered in the previous tutorial.
What You'll Be Creating
Picking up from the previous tutorial, you'll be adding a tab view for displaying the different portions of the app. Previously our app just had the Tracking page, so we didn't need tabs. In this post, we'll be adding the Walks page. This page will display the user's walking sessions. A new data point will be added here every time the user tracks their walking session. There will also be a function for clearing the data.
Here's what the final output will look like:
Setting Up the Project
If you have followed the previous tutorial on geolocation, you can simply use the same project and build the features that we will be adding in this tutorial. Otherwise, you can create a new project and copy the starter files into your project's app folder.
tns create fitApp --appid "com.yourname.fitApp"
After that, you also need to install the geolocation and Google Maps plugins:
tns plugin add nativescript-geolocation tns plugin add nativescript-google-maps-sdk
Once installed, you need to configure the Google Maps plugin. You can read the complete instructions on how to do this by reading the section on Installing the Google Maps Plugin in the previous tutorial.
Once all of those are done, you should be ready to follow along with this tutorial.
Running the Project
You can run the project by executing tns run android. But since this app will build on the geolocation functionality, I recommend you use a GPS emulator for quickly setting and changing your location. You can read about how to do so in the section on Running the App in the previous tutorial. 
Installing the SQLite Plugin
The first thing that you need to do to start working with SQLite is to install the plugin:
tns plugin add nativescript-sqlite
This allows you to do things like connecting to a database and doing CRUD (create, read, update, delete) operations on it.
Connecting to the Database
Open the main-page.js file and import the SQLite plugin:
var Sqlite = require("nativescript-sqlite");
You can now connect to the database:
var db_name = "walks.db"; new Sqlite(db_name).then(db => { // next: create table for storing walks data }, error => { });
The walks.db file was created from the terminal using the touch command, so it's just an empty file. Copy it into the app folder.
If it successfully connected, the promise's resolve function will be executed. Inside that, we run the SQL statement for creating the walks table. To keep things simple, all we need to save is the total distance covered (in meters) and the total steps, as well as the start and end timestamps. 
db.execSQL("CREATE TABLE IF NOT EXISTS walks (id INTEGER PRIMARY KEY AUTOINCREMENT, total_distance INTEGER, total_steps INTEGER, start_datetime DATETIME, end_datetime DATETIME)").then(id => { page.bindingContext = createViewModel(db); }, error => { console.log("CREATE TABLE ERROR", error); });
Once the query executes successfully, we pass the database instance (db) into the page context. This will allow us to use it from the main-view-model.js file later on.
Fetching Data
Now we're ready to work with the data. But since we'll be working with dates, we first need to install a library called fecha. This allows us to easily parse and format dates:
npm install --save fecha
Once it's installed, open the main-view-model.js file and include the library:
var fecha = require('fecha');
Next is the code for checking if geolocation is enabled. First, create a variable (walk_id) for storing the ID of a walking record. We need this because the app will immediately insert a new walk record into the walks table when the user starts location tracking. walk_id will store the ID that's auto-generated by SQLite so that we'll be able to update the record once the user stops tracking.
var walk_id;
Next, get the current month and year. We'll use it to query the table so it only returns records that are in the same month and year. This allows us to limit the number of records that appear in the UI.
var month = fecha.format(new Date(), 'MM'); //e.g 07 var year = fecha.format(new Date(), 'YYYY'); //e.g 2017
We also need a variable for storing the start timestamp. We'll use it later on to update the UI. This is because we're only querying the table once when the app is loaded, so we need to manually update the UI of any new data which becomes available. And since the starting timestamp will only have a value when the user starts tracking, we need to initialize it outside the scope so we can update or access its value later on.
var st_datetime; // start datetime
Initialize the walks data that will be displayed in the UI:
var walks = []; viewModel.walks = []; viewModel.has_walks = false;
Get the data from the walks table using the all() method. Here, we're supplying the month and the year as query parameters. The strftime() function is used to extract the month and year part of the start_datetime. 
db.all( "SELECT * FROM walks WHERE strftime('%m', start_datetime) == ? AND strftime('%Y', start_datetime) == ? ORDER BY start_datetime DESC", [month, year] ).then((err, rs) => { if(!err){ // next: update the UI with the walks data } });
Once a success response is returned, we loop through the result set so that we can format the data correctly. Note that the indexes in which we access the individual values depend on the table structure that was described earlier in the main-page.js file. The first column is ID, the second is the total distance, and so on.
The formatted data is then pushed to the walks array and is used to update the UI. has_walks is used as a toggle for the UI so that we can show or hide things based on its value.
rs.forEach((w) => { let start_datetime = new Date(w[3]); let end_datetime = new Date(w[4]); walks.push({ start: fecha.format(start_datetime, 'MMM D, h:mm'), // e.g Jun 5, 5:30 end: fecha.format(end_datetime, 'h:mm a'), // e.g 6:30 pm distance: commafy(w[1]) + 'm', // e.g 2,000m steps: commafy(w[2]) // e.g 2,300 }); }); if(walks.length){ viewModel.set('has_walks', true); } viewModel.set('walks', walks);
This will supply the data for the ListView in the main-page.xml file:
<ListView items=""> <ListView.itemTemplate> <GridLayout columns="2*,*,*" rows="auto" class="item item-row"> <Label text="" textWrap="true" row="0" col="0"/> <Label text="" textWrap="true" row="0" col="1" /> <Label text="" textWrap="true" row="0" col="2" /> </GridLayout> </ListView.itemTemplate> </ListView>
Saving Data
Once the user starts tracking, set the current datetime as the start_datetime and insert initial values into the table using the execSQL() function. Just like the all() function, this expects the SQL query as the first argument and an array of parameters as the second.
If the query is successful, it should return the auto-generated ID for the inserted record. We then assign it as the value for the walk_id so it can be used later on to update this specific record.
st_datetime = new Date(); var start_datetime = fecha.format(st_datetime, 'YYYY-MM-DD HH:mm:ss'); db.execSQL( "INSERT INTO walks (total_distance, total_steps, start_datetime) VALUES (?, ?, ?)", [0, 0, start_datetime] ).then((id) => { walk_id = id; }, (e) => { dialogs.alert(e.message); });
Once the user stops tracking, we again get the current timestamp and format it accordingly for storage:
var ed_datetime = new Date(); var end_datetime = fecha.format(ed_datetime, 'YYYY-MM-DD HH:mm:ss');
Since we've ordered the results from most to least recent, we use unshift() (instead of push()) to add the new item to the top of the walks array.
walks.unshift({ start: fecha.format(st_datetime, 'MMM D, h:mm'), end: fecha.format(ed_datetime, 'h:mm a'), distance: commafy(total_distance) + 'm', steps: commafy(total_steps) }); viewModel.set('walks', walks); if(walks.length > 0){ viewModel.set('has_walks', true); }
After that, we once again we use the execSQL() function to update the record that we inserted earlier:
db.execSQL( "UPDATE walks SET end_datetime = ?, total_steps = ?, total_distance = ? WHERE id = ?", [end_datetime, total_steps, total_distance, walk_id] ).then( (err, id) => { if(!err){ // todo: add code for resetting the tracking UI here } } );
Be sure to move the code for resetting the tracking UI (to reset the total distance and steps) inside the promise's resolve function so you can easily test whether the update query executed successfully or not. 
Clearing Data
Deleting data is done by clicking on the Clear Data button below the list of walk data:
<ListView items=""> ... </ListView> <Button text="Clear Data" tap="" class="bg-danger" />
In the main-view-model.js file, add the code for deleting all the data from the walks table. If you're used to MySQL, you might be wondering why we're using the DELETE query instead of TRUNCATE for emptying the table. Well, that's because SQLite doesn't have the TRUNCATE function. That's why we have to use the DELETE query without supplying a condition so that it will delete all the records that are currently in the table. 
viewModel.clearData = function() { dialogs.confirm("Are you sure you want to clear your data?").then((agree) => { if(agree){ db.execSQL("DELETE FROM walks", []).then( (err) => { if(!err){ dialogs.alert("Data has been cleared!"); walks = []; viewModel.set('walks', []); viewModel.set('has_walks', false); } } ); } }); }
Conclusion
In this tutorial, you've learned how to locally store data in your NativeScript apps using the SQLite plugin. As you have seen, SQLite allows you to reuse your existing SQL skills in managing a local database. It's important to note that not all functions that you're used to in MySQL are supported in SQLite. So it's always wise to consult the documentation if you're not sure whether a certain function is supported or not. 
If you want to learn about other options for storing data in NativeScript apps, I recommend you read this article on Going Offline With NativeScript.
In the final post of this series, we'll add push notifications to our app.
In the meantime, check out some of our other posts on NativeScript and cross-platform mobile coding.
Mobile App
Create a Weather App With TypeScript and NativeScript
Wernher-Bel Ancheta
Mobile Development
Introducing Vue and Weex for Native Mobile Apps
Lawrence Turton
Ionic
Get Started With Ionic Services: Auth
Wernher-Bel Ancheta
For a comprehensive introduction to NativeScript, try our video course Code a Mobile App With NativeScript. In this course, Keyvan Kasaei will show you step by step how to build a simple application. Along the way, you'll learn how to implement a simple app workflow with network requests, an MVVM architecture, and some of the most important NativeScript UI components. By the end, you'll understand why you should consider NativeScript for your next mobile app project.
  http://j.mp/2tBd5DL via Envato Tuts+ Code URL : http://j.mp/2etecmc
0 notes
iyarpage · 7 years ago
Text
Android SDK Versions Tutorial with Kotlin
Update Note: This tutorial has been updated to Kotlin by Eric Crawford. The original tutorial was written by Eunice Obugyei.
Ever since the first release of Android, the range of supported devices has grown to represent a wide array of phones, smart watches and more. Everything necessary to start developing Android applications for those devices falls under one specification called the Android SDK (software development kit). New SDK versions are released with each new version of Android, and that is our focus for this tutorial.
These new sdk versions take advantage of the increased processing power available on the latest devices to provide great new features. Awesome, right? The flip side, of course, is that Android developers face the challenge of making sure an application will work on a range of devices running different versions of the Android SDK.
Luckily, there are some best practice guidelines and tools to help get the work done without compromising on UX or deadlines.
In this Android SDK Versions tutorial you’ll learn about:
Android SDK versions and API Levels
Android Support Libraries and their importance
How to use the Android Support Library to make an app backward-compatible
How to use the CardView Support Library
To put theory into practice, you’ll play with a simple application called Continents. Continents gives short descriptions of the continents in the world.
Note: This Android SDK Versions tutorial assumes you are already familiar with the basics of Android development and Kotlin. If you are completely new to Android development, read Beginning Android Development and our other Android and Kotlin tutorials to familiarize yourself with the basics.
Note: This tutorial requires Android Studio 3.0 Beta 4 or later.
Getting Started
Download the starter project for this tutorial and extract the downloaded zip file to get the project.
Select Open an existing Android Studio project from the Quick Start menu to open the starter project:
If you are on a windows machine you can also select File \ Open. Navigate to and select the starter project folder.
If you are prompted to install (or update to) a new version of the Android build tools, or are prompted to update your Gradle plugin version, please do so.
Build and run the project on the emulator or device to make sure it compiles and runs correctly.
Emulating Different SDK Versions
We want to try running the sample app on different Android versions. But it’s unlikely anyone has an Android device for every single API Level of the SDK. So first, let’s learn how to set up emulators with different SDK versions.
To set up an emulator, locate the AVD (Android Virtual Device) Manager on the Android Studio Toolbar.
Creating A Virtual Device
If the Toolbar is not showing, select View \ Toolbar to show it. You can also select select Tools \ Android \ AVD Manager to open the AVD Manager.
Click the Create a virtual device button. That will open the Select Hardware section of the Virtual Device Configuration window.
Select a device of your choice and click Next. That opens the System Image section, which currently shows recommended system images.
The x86 Images tab will list all the x86 emulator images. The Other Images tab will show both ARM and x86 emulator images.
Note: It is recommended to always use x86 images. These will run the fastest on most pc and mac computers
Downloading A System Image
To download a system image that you do not already have installed, click the Download link by the release name.
Notice that the Android platform currently has fifteen major versions of the SDK . Starting with Android 1.5, major versions of the SDK have been developed under a confectionery-themed code name. Google has managed to choose these code names in an alphabetical order. They haven’t run out of names of sweets yet :]
Each version (minor or major) of the Android SDK has an integer value that uniquely identifies it. This unique identifier is referred to as the API Level. The higher the API Level, the later the version. For developers, API Level is important because it is what determines the range of devices an app can run on.
Let’s look at an example, the Android 8.0 release. We can see that:
It is the most recent version of the Android SDK
Its version number is 8.0
Its code name is Oreo
It has an API Level of 26
For this tutorial, we will need at least two emulators, one with API Level 15 and another one with API Level 26.
Going back to the System Image screen in Android Studio, click the Download button for each of the SDK versions you will need for this tutorial that you have not already downloaded (Level 15 and Level 26). Then select the system image for Level 26 and click Next.
On the next screen, click Finish.
Repeat the same steps to setup an emulator with API level 15. You may choose one with API level 16 instead if you are unable to download one with API level 15.
First Run
Try running the sample app on the emulator running API Level 26:
It all looks great, right? But if you were to try and run the app on a device with API Level lower than 26 it wouldn’t run. This is because the app only runs on devices that run Android API Level 26 and upwards, which isn’t great for older devices. Later, you’ll learn how to extend the app’s support from Android API Level 26 to as low as Android API Level 14.
SDK Versions and API Levels
As mentioned earlier, the API Level is a unique integer that identifies a specific version of the Android SDK. Let’s look at how to specify API Levels in Android Studio to compile and release an application.
Open build.gradle for the app module:
Here we can see three important attributes:
minSdkVersion is the minimum API Level with which the app is compatible. The Android system will prevent a user from installing the application if the system’s API Level is lower than the value specified in this attribute. Android requires the minSdkVersion attribute to always be set.
targetSdkVersion is the API Level that the application targets. This attribute informs the system that you have tested against the target version. The targetSdkVersion defaults to the minSdkVersion if not specified.
compileSdkVersion specifies the API Level to compile the application against.
These attributes can be adjusted in the app modules build.gradle file.
Note on SDK previews: It’s important to know that when you set the compileSdkVersion to a preview release of the Android framework, Android Studio will force the minSdkVersion and targetSdkVersion to equal the exact same string as compileSdkVersion. This policy is necessary to prevent situations where you might upload your app to the Google Play Store. As a result, you can only run applications where compileSdkVersion is set to a preview release on emulators with that exact same preview and you won’t be able to run it on older devices.
Backward Compatibility
The Android SDK is by default forward compatible but not backward compatible — this means that an application that is built with and supports a minimum SDK version of 3.0 can be installed on any device running Android versions 3.0 and upwards. But not on devices running Android versions below 3.0.
Since the Android SDK is not backward compatible, you should choose the minimum SDK carefully. This means striking a balance between supporting a wide range of devices and designing an app that implements useful features in later SDK versions.
For example, when Android 3.0 was released in 2011, the Action Bar was unleashed on the Android Community. Since the Action Bar was only supported in Android 3.0 and later, using it in an app meant choosing either a cool user interface or supporting devices that ran older versions of the SDK. Sometimes you can’t have your honeycomb and eat it too :[
Or can you? To help with the Action Bar issue, the Android Support Library introduced a backward-compatible version in the v7-appcompat support library. So it would allow developers to support older versions of the SDK and still use the latest Action Bar APIs in their apps. Sweet! Honeycomb for everyone!
Let’s take a deeper look at what the Support Library does and how it works.
Note on picking minimum sdk version: Google provides a Dashboard that breaks down the user distribution percentage per api level. You can use this to help target a good percentage of users.
Android Support Libraries
A wide range of components make up what is referred to as the “Support Library” or “Support Libraries,” and they can be categorized in two groups:
The AppCompat library: The intention here is to make sure all (or most) of the framework APIs for the latest API Level have been backported to earlier versions and can be found in this single library. The first version of AppCompat was released at Google I/O 2013.
The goal of this first release was to allow developers to backport the ActionBar to devices running IceScreamSandwich level. This gave API parity to the framework across as many API Levels as possible. Since then, the AppCompat library has continued to evolve. And with Android L the support library is now at the point where the API is equivalent to the framework itself — the first time that has ever happened :]
Others: The rest of the libraries that make up the Support Library essentially provide new functionality with the same consideration for backward compatibility (palette, gridview, gridlayout, recycler view, material design widgets).
When you break these up into independent libraries, you can pick and choose the ones you need in your project. It’s important to note that each support library is backward-compatible to a specific API Level. And they are usually named based on which API Level they are backward-compatible to. For example, v7-appcompat provides backward compatibility to API Level 7.
You can find the full list of components that fall under the Support Library in the Android documentation.
Note: Support Library minimum sdk change: Beginning with Support Library release 26.0.0, Google has changed the minimum supported level to Api 14. This means that your minimum sdk version cannot be set below Api level 14 when using version 26.0.0+ of the Support Library.
How to Use an Android Support Library
Time to see an Android support library in action! Open MainActivity.kt. As you may have noticed in the onCreate() method, the app uses a Toolbar (which is part of the material design patterns) instead of an Action Bar.
The Toolbar was added in API 21 (Android Lollipop) as a flexible widget that can be used anywhere in layouts, be animated and change in size, unlike the Action Bar.
Thanks to AppCompat, that feature has been back-ported all the way to API 14, which is code-named Ice Cream Sandwich (are you hungry yet?). You’re going to use the v7-appcompat support library to extend your app’s compatibility to a minSdkVersion of 15.
Update Build File
First add google() to the Maven repositories in your project-level build.gradle file, if it’s not already there:
repositories { jcenter() google() }
Now, open build.gradle for the app module and add the following to the dependencies section:
implementation "com.android.support:appcompat-v7:26.0.1"
By adding this, you’re declaring the appcompat-v7 support library as a dependency for your application. You can ignore the warning to use a newer version of the Support library. Though you may update, it’s recommended you stick to the one in this tutorial.
Next, change the minSdkVersion attribute to 15.
minSdkVersion 15
Here you’re declaring that the app should be able to run on devices with Android SDK version 4.0.4. Now try running your application on an emulator running API Level 15. You should see the following exceptions in the logcat:
The important line to look for is:
Caused by: java.lang.ClassNotFoundException: android.widget.Toolbar
The ClassNotFoundException error indicates that there is no such class in the SDK version you’re running the app against. Indeed, it’s only available in API Level 21, while you’re currently running API Level 15.
Update For Backward Compatibility
You’re going to update the code to use the backward-compatible version of Toolbar. In MainActivity.kt, and update the android.widget.Toolbar import statement to match the following:
import android.support.v7.widget.Toolbar
This replaces the SDK import with one from the AppCompat library.
Next import the AppCompatActivity from the AppCompat library:
import android.support.v7.app.AppCompatActivity
Next update the MainActivity class definition line so that it inherits from AppCompatActivity:
class MainActivity : AppCompatActivity(), ContinentSelectedListener
Once again, you’re replacing a class from the latest SDKs with one that exists in the support library.
You now need to work through the class and replace some method calls with their support library equivalents:
Find the call to setActionBar(toolbar) at the end of the onCreate() method body and update it to setSupportActionBar(toolbar).
Find the calls to actionBar? in onContinentSelected(), goToContinentList(), and onBackPressed() and replace both of them with supportActionBar?.
Replace the calls to fragmentManager() in onContinentSelected() and goToContinentList() with supportFragmentManager().
Update Fragment Classes
Open DescriptionFragment.kt and MainFragment.kt, find the following line:
import android.app.Fragment
and update to match the following:
import android.support.v4.app.Fragment
Here you’re using the support version of the Fragment class instead of the one in the main SDK. The latter can only be used in apps with a minSdkVersion of 14 and above.
Note: AppCompat v7 depends on the v4 Support Library. That’s why you can also use all the APIs in the android.support.v4.app package.
So far you’ve replaced all the main API calls with corresponding methods from the support library. Next you will need to update your layout files to use the Support Library.
In the res / layout folder, open toolbar_custom.xml and do the following:
Change android.widget.Toolbar to android.support.v7.widget.Toolbar
Change ?android:attr/actionBarSize to ?attr/actionBarSize
Again, all this does is change the package name from android to v7-appcompat.
Now that all of the compile-time errors have been checked and fixed, try to run the app again. You will now get the following run-time error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.raywenderlich.continents/com.raywenderlich.continents.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Update Styles
The error message is pretty self-explanatory, but why do you need to use the AppCompat theme? A feature from the Lollipop release of AppCompat is the different approach to theming. One of the interesting things about this is the capability to get an L-friendly version of your app on prior versions. If an app uses the framework version of everything (Activity instead of AppCompatActivity for example), it would only get the material theme on phones with the L release. Devices with prior releases would get the default theme for those releases. The goal of the AppCompat theming feature is to have a consistent experience across all devices.
In the res\values folder, open styles.xml, and change android:Theme.Black.NoTitleBar to Theme.AppCompat.NoActionBar.
Now build and run. You can test the app on an API 15 device or emulator as well.
Well done! The sample app is now backward compatible. Ice cream sandwich and lollipops and jelly beans for everyone!
Let’s throw in some cards to make the detail screen look nicer.
How to Use the Card View Support Library
Open build.gradle for the app module and add the following to the dependencies section:
implementation "com.android.support:cardview-v7:26.0.1"
Adding this declares the v7-cardview support library as a dependency for the application.
Open the fragment_description.xml file and place the ImageView in a CardView:
<android.support.v7.widget.CardView android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_gravity="center" android:layout_weight="1" card_view:cardBackgroundColor="#316130" card_view:cardElevation="20dp"> <ImageView android:id="@+id/continentImage" android:layout_width="match_parent" android:layout_height="match_parent" android:contentDescription="@string/continent_image_description" android:paddingBottom="@dimen/activity_vertical_margin" android:src="@drawable/africa" /> </android.support.v7.widget.CardView>
Notice that when using widgets from the Support Library, some XML attributes (cardBackgroundColor and cardElevation for the CardView) are not prefixed with “android.” That’s because they come from the Support Library API as opposed to the Android framework. Hit option+return (or Alt+Enter on PC) if you need to setup the card_view namespace in the xml file.
Now, build and run the project:
Cool, you’ve added this new-style cardview to your app and using the compatibility library it works from modern versions of Android, right back to ancient API-level 15.
Did You Say Material Design?
You’ve successfully used the AppCompat theming to give the app the Android Lollipop look and feel across a wide range of SDK versions. In addition to these elements, the Material Design specification includes many more patterns and widgets not contained in AppCompat. This is where the Design Library comes into play. It provides widgets such as navigation drawers, floating action buttons, snackbars and tabs. Let’s include it in the project and add a floating action button.
In build.gradle add the following in the dependencies section:
implementation "com.android.support:design:26.0.1"
Next add the following XML element above the closing tag for FrameLayout in fragment_description.xml:
<android.support.design.widget.FloatingActionButton android:id="@+id/search_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_search_white_18dp" />
Build and run. You will see the floating button as expected.
Backport All the Things?
Some features in the latest releases of the SDK are just too complex to backport. Ultimately, it’s your call to strike the right balance between performance and usability. If you find yourself wanting to use an unavailable framework API, you can check for the API Level at run-time.
For the following snippet from MainActivity, import the classes from the base package instead of the Support Library package. Then in the onContinentSelected, add the following after the description fragment is instantiated but before the fragment transaction:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { descriptionFragment.enterTransition = Fade() mainFragment?.exitTransition = Fade() descriptionFragment.exitTransition = Slide(Gravity.BOTTOM) mainFragment?.reenterTransition = Fade() descriptionFragment.allowReturnTransitionOverlap = true }
Build and run on both emulators. You should see no animations on the emulator running API Level 15, but notice the fade in and slide out on the emulators running API Level 25 and above:
Where To Go From Here?
Congratulations! Finally you’ve learned about Android SDK versions and their sweet code names. You made an API Level 26 application backward-compatible to API Level 15, and used the cardview and design library to add additional components. You might also have a sugar craving :]
Blame Android.
The final project for this Android SDK Versions tutorial can be downloaded here.
If you are interested in Android SDK version history, check out this wikipedia page or the versions page on the Android developer site. You can also read further about the minSdkVersion and targetSdkVersion attributes from the manifest page on the developer site. Finally, check out the developer pages on Support libraries and its feature list.
We hope you enjoyed this Android SDK Versions tutorial, and if you have any questions or comments, please join the forum discussion below!
The post Android SDK Versions Tutorial with Kotlin appeared first on Ray Wenderlich.
Android SDK Versions Tutorial with Kotlin published first on http://ift.tt/2fA8nUr
0 notes
iyarpage · 7 years ago
Text
Android SDK Versions Tutorial with Kotlin
Update Note: This tutorial has been updated to Kotlin by Eric Crawford. The original tutorial was written by Eunice Obugyei.
Ever since the first release of Android, the range of supported devices has grown to represent a wide array of phones, smart watches and more. Everything necessary to start developing Android applications for those devices falls under one specification called the Android SDK (software development kit). New SDK versions are released with each new version of Android, and that is our focus for this tutorial.
These new sdk versions take advantage of the increased processing power available on the latest devices to provide great new features. Awesome, right? The flip side, of course, is that Android developers face the challenge of making sure an application will work on a range of devices running different versions of the Android SDK.
Luckily, there are some best practice guidelines and tools to help get the work done without compromising on UX or deadlines.
In this Android SDK Versions tutorial you’ll learn about:
Android SDK versions and API Levels
Android Support Libraries and their importance
How to use the Android Support Library to make an app backward-compatible
How to use the CardView Support Library
To put theory into practice, you’ll play with a simple application called Continents. Continents gives short descriptions of the continents in the world.
Note: This Android SDK Versions tutorial assumes you are already familiar with the basics of Android development and Kotlin. If you are completely new to Android development, read Beginning Android Development and our other Android and Kotlin tutorials to familiarize yourself with the basics.
Note: This tutorial requires Android Studio 3.0 Beta 4 or later.
Getting Started
Download the starter project for this tutorial and extract the downloaded zip file to get the project.
Select Open an existing Android Studio project from the Quick Start menu to open the starter project:
If you are on a windows machine you can also select File \ Open. Navigate to and select the starter project folder.
If you are prompted to install (or update to) a new version of the Android build tools, or are prompted to update your Gradle plugin version, please do so.
Build and run the project on the emulator or device to make sure it compiles and runs correctly.
Emulating Different SDK Versions
We want to try running the sample app on different Android versions. But it’s unlikely anyone has an Android device for every single API Level of the SDK. So first, let’s learn how to set up emulators with different SDK versions.
To set up an emulator, locate the AVD (Android Virtual Device) Manager on the Android Studio Toolbar.
Creating A Virtual Device
If the Toolbar is not showing, select View \ Toolbar to show it. You can also select select Tools \ Android \ AVD Manager to open the AVD Manager.
Click the Create a virtual device button. That will open the Select Hardware section of the Virtual Device Configuration window.
Select a device of your choice and click Next. That opens the System Image section, which currently shows recommended system images.
The x86 Images tab will list all the x86 emulator images. The Other Images tab will show both ARM and x86 emulator images.
Note: It is recommended to always use x86 images. These will run the fastest on most pc and mac computers
Downloading A System Image
To download a system image that you do not already have installed, click the Download link by the release name.
Notice that the Android platform currently has fifteen major versions of the SDK . Starting with Android 1.5, major versions of the SDK have been developed under a confectionery-themed code name. Google has managed to choose these code names in an alphabetical order. They haven’t run out of names of sweets yet :]
Each version (minor or major) of the Android SDK has an integer value that uniquely identifies it. This unique identifier is referred to as the API Level. The higher the API Level, the later the version. For developers, API Level is important because it is what determines the range of devices an app can run on.
Let’s look at an example, the Android 8.0 release. We can see that:
It is the most recent version of the Android SDK
Its version number is 8.0
Its code name is Oreo
It has an API Level of 26
For this tutorial, we will need at least two emulators, one with API Level 15 and another one with API Level 26.
Going back to the System Image screen in Android Studio, click the Download button for each of the SDK versions you will need for this tutorial that you have not already downloaded (Level 15 and Level 26). Then select the system image for Level 26 and click Next.
On the next screen, click Finish.
Repeat the same steps to setup an emulator with API level 15. You may choose one with API level 16 instead if you are unable to download one with API level 15.
First Run
Try running the sample app on the emulator running API Level 26:
It all looks great, right? But if you were to try and run the app on a device with API Level lower than 26 it wouldn’t run. This is because the app only runs on devices that run Android API Level 26 and upwards, which isn’t great for older devices. Later, you’ll learn how to extend the app’s support from Android API Level 26 to as low as Android API Level 14.
SDK Versions and API Levels
As mentioned earlier, the API Level is a unique integer that identifies a specific version of the Android SDK. Let’s look at how to specify API Levels in Android Studio to compile and release an application.
Open build.gradle for the app module:
Here we can see three important attributes:
minSdkVersion is the minimum API Level with which the app is compatible. The Android system will prevent a user from installing the application if the system’s API Level is lower than the value specified in this attribute. Android requires the minSdkVersion attribute to always be set.
targetSdkVersion is the API Level that the application targets. This attribute informs the system that you have tested against the target version. The targetSdkVersion defaults to the minSdkVersion if not specified.
compileSdkVersion specifies the API Level to compile the application against.
These attributes can be adjusted in the app modules build.gradle file.
Note on SDK previews: It’s important to know that when you set the compileSdkVersion to a preview release of the Android framework, Android Studio will force the minSdkVersion and targetSdkVersion to equal the exact same string as compileSdkVersion. This policy is necessary to prevent situations where you might upload your app to the Google Play Store. As a result, you can only run applications where compileSdkVersion is set to a preview release on emulators with that exact same preview and you won’t be able to run it on older devices.
Backward Compatibility
The Android SDK is by default forward compatible but not backward compatible — this means that an application that is built with and supports a minimum SDK version of 3.0 can be installed on any device running Android versions 3.0 and upwards. But not on devices running Android versions below 3.0.
Since the Android SDK is not backward compatible, you should choose the minimum SDK carefully. This means striking a balance between supporting a wide range of devices and designing an app that implements useful features in later SDK versions.
For example, when Android 3.0 was released in 2011, the Action Bar was unleashed on the Android Community. Since the Action Bar was only supported in Android 3.0 and later, using it in an app meant choosing either a cool user interface or supporting devices that ran older versions of the SDK. Sometimes you can’t have your honeycomb and eat it too :[
Or can you? To help with the Action Bar issue, the Android Support Library introduced a backward-compatible version in the v7-appcompat support library. So it would allow developers to support older versions of the SDK and still use the latest Action Bar APIs in their apps. Sweet! Honeycomb for everyone!
Let’s take a deeper look at what the Support Library does and how it works.
Note on picking minimum sdk version: Google provides a Dashboard that breaks down the user distribution percentage per api level. You can use this to help target a good percentage of users.
Android Support Libraries
A wide range of components make up what is referred to as the “Support Library” or “Support Libraries,” and they can be categorized in two groups:
The AppCompat library: The intention here is to make sure all (or most) of the framework APIs for the latest API Level have been backported to earlier versions and can be found in this single library. The first version of AppCompat was released at Google I/O 2013.
The goal of this first release was to allow developers to backport the ActionBar to devices running IceScreamSandwich level. This gave API parity to the framework across as many API Levels as possible. Since then, the AppCompat library has continued to evolve. And with Android L the support library is now at the point where the API is equivalent to the framework itself — the first time that has ever happened :]
Others: The rest of the libraries that make up the Support Library essentially provide new functionality with the same consideration for backward compatibility (palette, gridview, gridlayout, recycler view, material design widgets).
When you break these up into independent libraries, you can pick and choose the ones you need in your project. It’s important to note that each support library is backward-compatible to a specific API Level. And they are usually named based on which API Level they are backward-compatible to. For example, v7-appcompat provides backward compatibility to API Level 7.
You can find the full list of components that fall under the Support Library in the Android documentation.
Note: Support Library minimum sdk change: Beginning with Support Library release 26.0.0, Google has changed the minimum supported level to Api 14. This means that your minimum sdk version cannot be set below Api level 14 when using version 26.0.0+ of the Support Library.
How to Use an Android Support Library
Time to see an Android support library in action! Open MainActivity.kt. As you may have noticed in the onCreate() method, the app uses a Toolbar (which is part of the material design patterns) instead of an Action Bar.
The Toolbar was added in API 21 (Android Lollipop) as a flexible widget that can be used anywhere in layouts, be animated and change in size, unlike the Action Bar.
Thanks to AppCompat, that feature has been back-ported all the way to API 14, which is code-named Ice Cream Sandwich (are you hungry yet?). You’re going to use the v7-appcompat support library to extend your app’s compatibility to a minSdkVersion of 15.
Update Build File
First add google() to the Maven repositories in your project-level build.gradle file, if it’s not already there:
repositories { jcenter() google() }
Now, open build.gradle for the app module and add the following to the dependencies section:
implementation "com.android.support:appcompat-v7:26.0.1"
By adding this, you’re declaring the appcompat-v7 support library as a dependency for your application. You can ignore the warning to use a newer version of the Support library. Though you may update, it’s recommended you stick to the one in this tutorial.
Next, change the minSdkVersion attribute to 15.
minSdkVersion 15
Here you’re declaring that the app should be able to run on devices with Android SDK version 4.0.4. Now try running your application on an emulator running API Level 15. You should see the following exceptions in the logcat:
The important line to look for is:
Caused by: java.lang.ClassNotFoundException: android.widget.Toolbar
The ClassNotFoundException error indicates that there is no such class in the SDK version you’re running the app against. Indeed, it’s only available in API Level 21, while you’re currently running API Level 15.
Update For Backward Compatibility
You’re going to update the code to use the backward-compatible version of Toolbar. In MainActivity.kt, and update the android.widget.Toolbar import statement to match the following:
import android.support.v7.widget.Toolbar
This replaces the SDK import with one from the AppCompat library.
Next import the AppCompatActivity from the AppCompat library:
import android.support.v7.app.AppCompatActivity
Next update the MainActivity class definition line so that it inherits from AppCompatActivity:
class MainActivity : AppCompatActivity(), ContinentSelectedListener
Once again, you’re replacing a class from the latest SDKs with one that exists in the support library.
You now need to work through the class and replace some method calls with their support library equivalents:
Find the call to setActionBar(toolbar) at the end of the onCreate() method body and update it to setSupportActionBar(toolbar).
Find the calls to actionBar? in onContinentSelected(), goToContinentList(), and onBackPressed() and replace both of them with supportActionBar?.
Replace the calls to fragmentManager() in onContinentSelected() and goToContinentList() with supportFragmentManager().
Update Fragment Classes
Open DescriptionFragment.kt and MainFragment.kt, find the following line:
import android.app.Fragment
and update to match the following:
import android.support.v4.app.Fragment
Here you’re using the support version of the Fragment class instead of the one in the main SDK. The latter can only be used in apps with a minSdkVersion of 14 and above.
Note: AppCompat v7 depends on the v4 Support Library. That’s why you can also use all the APIs in the android.support.v4.app package.
So far you’ve replaced all the main API calls with corresponding methods from the support library. Next you will need to update your layout files to use the Support Library.
In the res / layout folder, open toolbar_custom.xml and do the following:
Change android.widget.Toolbar to android.support.v7.widget.Toolbar
Change ?android:attr/actionBarSize to ?attr/actionBarSize
Again, all this does is change the package name from android to v7-appcompat.
Now that all of the compile-time errors have been checked and fixed, try to run the app again. You will now get the following run-time error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.raywenderlich.continents/com.raywenderlich.continents.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Update Styles
The error message is pretty self-explanatory, but why do you need to use the AppCompat theme? A feature from the Lollipop release of AppCompat is the different approach to theming. One of the interesting things about this is the capability to get an L-friendly version of your app on prior versions. If an app uses the framework version of everything (Activity instead of AppCompatActivity for example), it would only get the material theme on phones with the L release. Devices with prior releases would get the default theme for those releases. The goal of the AppCompat theming feature is to have a consistent experience across all devices.
In the res\values folder, open styles.xml, and change android:Theme.Black.NoTitleBar to Theme.AppCompat.NoActionBar.
Now build and run. You can test the app on an API 15 device or emulator as well.
Well done! The sample app is now backward compatible. Ice cream sandwich and lollipops and jelly beans for everyone!
Let’s throw in some cards to make the detail screen look nicer.
How to Use the Card View Support Library
Open build.gradle for the app module and add the following to the dependencies section:
implementation "com.android.support:cardview-v7:26.0.1"
Adding this declares the v7-cardview support library as a dependency for the application.
Open the fragment_description.xml file and place the ImageView in a CardView:
<android.support.v7.widget.CardView android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_gravity="center" android:layout_weight="1" card_view:cardBackgroundColor="#316130" card_view:cardElevation="20dp"> <ImageView android:id="@+id/continentImage" android:layout_width="match_parent" android:layout_height="match_parent" android:contentDescription="@string/continent_image_description" android:paddingBottom="@dimen/activity_vertical_margin" android:src="@drawable/africa" /> </android.support.v7.widget.CardView>
Notice that when using widgets from the Support Library, some XML attributes (cardBackgroundColor and cardElevation for the CardView) are not prefixed with “android.” That’s because they come from the Support Library API as opposed to the Android framework. Hit option+return (or Alt+Enter on PC) if you need to setup the card_view namespace in the xml file.
Now, build and run the project:
Cool, you’ve added this new-style cardview to your app and using the compatibility library it works from modern versions of Android, right back to ancient API-level 15.
Did You Say Material Design?
You’ve successfully used the AppCompat theming to give the app the Android Lollipop look and feel across a wide range of SDK versions. In addition to these elements, the Material Design specification includes many more patterns and widgets not contained in AppCompat. This is where the Design Library comes into play. It provides widgets such as navigation drawers, floating action buttons, snackbars and tabs. Let’s include it in the project and add a floating action button.
In build.gradle add the following in the dependencies section:
implementation "com.android.support:design:26.0.1"
Next add the following XML element above the closing tag for FrameLayout in fragment_description.xml:
<android.support.design.widget.FloatingActionButton android:id="@+id/search_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_search_white_18dp" />
Build and run. You will see the floating button as expected.
Backport All the Things?
Some features in the latest releases of the SDK are just too complex to backport. Ultimately, it’s your call to strike the right balance between performance and usability. If you find yourself wanting to use an unavailable framework API, you can check for the API Level at run-time.
For the following snippet from MainActivity, import the classes from the base package instead of the Support Library package. Then in the onContinentSelected, add the following after the description fragment is instantiated but before the fragment transaction:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { descriptionFragment.enterTransition = Fade() mainFragment?.exitTransition = Fade() descriptionFragment.exitTransition = Slide(Gravity.BOTTOM) mainFragment?.reenterTransition = Fade() descriptionFragment.allowReturnTransitionOverlap = true }
Build and run on both emulators. You should see no animations on the emulator running API Level 15, but notice the fade in and slide out on the emulators running API Level 25 and above:
Where To Go From Here?
Congratulations! Finally you’ve learned about Android SDK versions and their sweet code names. You made an API Level 26 application backward-compatible to API Level 15, and used the cardview and design library to add additional components. You might also have a sugar craving :]
Blame Android.
The final project for this Android SDK Versions tutorial can be downloaded here.
If you are interested in Android SDK version history, check out this wikipedia page or the versions page on the Android developer site. You can also read further about the minSdkVersion and targetSdkVersion attributes from the manifest page on the developer site. Finally, check out the developer pages on Support libraries and its feature list.
We hope you enjoyed this Android SDK Versions tutorial, and if you have any questions or comments, please join the forum discussion below!
The post Android SDK Versions Tutorial with Kotlin appeared first on Ray Wenderlich.
Android SDK Versions Tutorial with Kotlin published first on http://ift.tt/2fA8nUr
0 notes