We are going to use multiple libraries and backend services to build Our wordpress blog app such as Android, GBEAPI, WordPress, Kotlin, Volley,HTML,CSS,JS, retrofit2, Gson, Okhttp, Picasso, Ahbottomnavigation.
Follow the following steps:
1-reserve your WordPress hosting from greenbackend and get your wordpress greenbackend api license
http://greenbackend.com/developer-hosting
2-create a new android project using android studio and replace grandle build with the following gradle build file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.ahmadnaser.gbeapiswpblogdemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.android.support:cardview-v7:26.1.0'
//3rd party libs
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.aurelhubert:ahbottomnavigation:1.3.3'
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation "com.squareup.retrofit2:converter-gson:2.3.0"
implementation 'com.android.volley:volley:1.0.0'
}
3-Create a new class for app
GBEAPISDemo
package com.ahmadnaser.gbeapiswpblogdemo
//package com.ahmadnaser.gbeapisdemo
///Developed By Ahmad Naser Turnkey Solutions LLC - Greenbackend.com
///http://greenbackend.com/developer-hosting
///www.greenbackend.com
// www.khottah.com
///greenbackend@ahmadnaser.com
///copyright © 2018 ANTS LLC. All rights reserved.
import android.app.Application
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.toolbox.Volley
class GBEAPISDemo:Application(){
companion object {
private val TAG = GBEAPISDemo::class.java.simpleName
//define signleton app
@get:Synchronized var instance: GBEAPISDemo? = null
private set
}
val requestQueue: RequestQueue? = null
get() {
if(field == null){
return Volley.newRequestQueue(applicationContext)
}
return field
}
override fun onCreate() {
super.onCreate()
instance = this
}
fun <T> addToRequestQueue(request: Request<T>){
request.tag = TAG
requestQueue?.add(request)
}
}
4-Add app and permissions to your manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ahmadnaser.gbeapiswpblogdemo">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name="com.ahmadnaser.gbeapiswpblogdemo.GBEAPISDemo"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.ahmadnaser.gbeapiswpblogdemo.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
5-create main content in res/layout
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
6-add AHBottomNavigation to main activity layout and include the main content
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.ahmadnaser.gbeapiswpblogdemo.MainActivity">
<include layout="@layout/content_main" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="@+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</android.support.design.widget.CoordinatorLayout>
7- Add Tabs to MainActivity,
package com.ahmadnaser.gbeapiswpblogdemo
import android.graphics.Color
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.ahmadnaser.gbeapiswpblogdemo.fragments.CategoryFragment
import com.ahmadnaser.gbeapiswpblogdemo.fragments.FavFragment
import com.ahmadnaser.gbeapiswpblogdemo.fragments.HomeFragment
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(), AHBottomNavigation.OnTabSelectedListener {
override fun onTabSelected(position: Int, wasSelected: Boolean): Boolean {
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigation.setOnTabSelectedListener(this)
this.createNavItems()
}
private fun createNavItems() {
//CREATE ITEMS
val home = AHBottomNavigationItem("Home", R.drawable.ic_home_black_48dp)
val category = AHBottomNavigationItem("Category", R.drawable.ic_list_black_48dp)
//ADD PROPERTIES
navigation.addItem(home)
navigation.addItem(category)
//SET PROPERTIES
navigation.defaultBackgroundColor = Color.parseColor("#ffffff")
navigation.accentColor = Color.parseColor("#FFC107")
navigation.inactiveColor = Color.parseColor("#BDBDBD")
navigation.currentItem = 0
}
}
8- Create Config file for the urls
ANConfig Class
companion object {
val GBE_CAT_SINGLE_URL = "http://greenapis.com/gbe-api/v1/category/"
val GBE_ApiKey = "ApiKey"
val GBE_ApiKey_Value = "******"
}
9-create HomeFragment
package com.ahmadnaser.gbeapiswpblogdemo.fragments
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.ahmadnaser.gbeapiswpblogdemo.HomeAdapter
import com.ahmadnaser.gbeapiswpblogdemo.LatestPosts
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_home.*
import okhttp3.*
import java.io.IOException
class HomeFragment : Fragment() {
companion object {
fun newInstance(): HomeFragment {
var fragmentHome = HomeFragment()
return fragmentHome
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val rootView = inflater!!.inflate(R.layout.fragment_home, container, false)
val rv = rootView.findViewById<View>(R.id.recyclerView_home) as RecyclerView
rv.layoutManager = LinearLayoutManager(newInstance().context)
return rootView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fetchJson()
}
//Fetch JSON Data
private fun fetchJson() {
try {
println("Attempting to Fetch JSON")
//REST API Url
val url = "http://greenapis.com/gbe-api/v1/latest/"
val request = Request.Builder().url(url).addHeader("ApiKey","****").build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, LatestPosts::class.java)
if(activity != null) {
Thread {
activity.runOnUiThread { recyclerView_home.adapter = HomeAdapter(homeFeed) }
}.start()
}
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
catch (e: Exception) {
// handler
}
finally {
// optional finally block
}
}
}
fragment_home.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="blogapp.bittupatel.in.kotlinblogapp.com.ahmadnaser.gbeapiswpblogdemo.fragments.HomeFragment">
<!-- TODO: Update blank fragment layout -->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView_home"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
10-Create Models Posts, and View post_row.xml
Models.kt
class LatestPosts(val feed: List<Feed>) {
var total_items:Int = 0
var total_pages:Int = 0
}
class Feed( val id : Int,
val category : String,
val name : String,
val description : String,
val image : String,
val image_big : String,
val status : String,
val profilePic : String,
val timeStamp : Long,
val author : String,
val url : String,
val is_protected : Boolean,
val post_password : String )
post_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:id="@+id/cardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:minHeight="250dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/postThumbnail"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
android:id="@+id/postTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:text="Post Title"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" />
<TextView
android:id="@+id/authorName"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="Author Name"
android:textAppearance="@style/TextAppearance.Compat.Notification.Info" />
</LinearLayout>
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
11-Holder for HomeAdapter
package com.ahmadnaser.gbeapiswpblogdemo
import android.content.Intent
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.ahmadnaser.gbeapiswpblogdemo.R
class CustomHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var post: Feed? = null
var postTitle: TextView = itemView.findViewById<View>(R.id.postTitle) as TextView
var authorName: TextView = itemView.findViewById<View>(R.id.authorName) as TextView
var thumbnailImageView: ImageView = itemView.findViewById<View>(R.id.postThumbnail) as ImageView
companion object {
val POST_ID = "POST_ID"
val POST_IMAGE_KEY = "POST_IMAGE"
val POST_TITLE_KEY = "POST_TITLE"
val POST_AUTHOR_KEY = "POST_AUTHOR"
val POST_DATE_KEY = "POST_DATE"
val POST_CONTENT_KEY = "POST_CONTENT"
}
init {
itemView.setOnClickListener {
val intent = Intent(itemView.context, PostDetail::class.java)
intent.putExtra(POST_ID, post?.id)
intent.putExtra(POST_IMAGE_KEY, post?.image)
intent.putExtra(POST_TITLE_KEY, post?.name)
intent.putExtra(POST_AUTHOR_KEY, post?.author)
intent.putExtra(POST_DATE_KEY, post?.timeStamp.toString())
intent.putExtra(POST_CONTENT_KEY, post?.description)
itemView.context.startActivity(intent)
}
}
}
package com.ahmadnaser.gbeapiswpblogdemo
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.view.LayoutInflater
import android.view.ViewGroup
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.squareup.picasso.Picasso
class HomeAdapter(val homeFeed: LatestPosts) : RecyclerView.Adapter<CustomHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.post_row, parent, false)
return CustomHolder(v)
}
override fun onBindViewHolder(holder: CustomHolder?, position: Int) {
try {
val post = homeFeed.feed!!.get(position)
holder?.postTitle?.text = Html.fromHtml(post.name)
holder?.authorName?.text = post.timeStamp.toString() + " • " + post.author
Picasso.with(holder?.itemView?.context).load(post.image).into(holder?.thumbnailImageView)
holder?.post = post
}
catch (e: Exception) {
// handler
}
finally {
// optional finally block
}
}
override fun getItemCount(): Int {
return homeFeed.feed.count()
}
}
12-Call the fragment from main
class MainActivity : AppCompatActivity(), AHBottomNavigation.OnTabSelectedListener {
override fun onTabSelected(position: Int, wasSelected: Boolean): Boolean {
when (position) {
0 -> {
val fragment = HomeFragment.newInstance()
supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit()
}
}
return true
}
13-create CategoryFragment
package com.ahmadnaser.gbeapiswpblogdemo.fragments
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.ahmadnaser.gbeapiswpblogdemo.CategoryAdapter
import com.ahmadnaser.gbeapiswpblogdemo.CategoryFeed
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_category.*
import okhttp3.*
import java.io.IOException
class CategoryFragment : Fragment() {
companion object {
fun newInstance(): CategoryFragment {
val fragmentCat = CategoryFragment()
return fragmentCat
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val rootView = inflater!!.inflate(R.layout.fragment_category, container, false)
val rv = rootView.findViewById<View>(R.id.recyclerView_cat) as RecyclerView
rv.layoutManager = LinearLayoutManager(newInstance().context)
return rootView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fetchJson()
}
//Fetch JSON Data
private fun fetchJson() {
println("Attempting to Fetch JSON")
//REST API Url
val url = "http://greenapis.com/gbe-api/v1/categories"
val request = Request.Builder().url(url).addHeader("ApiKey","****").build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
val categoryFeed = gson.fromJson(body, CategoryFeed::class.java)
activity.runOnUiThread {
recyclerView_cat.adapter = CategoryAdapter(categoryFeed)
}
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
}
fragment_category.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ahmadnaser.gbeapiswpblogdemo.fragments.CategoryFragment">
<!-- TODO: Update blank fragment layout -->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView_cat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
14- Category Models, CategoryAdapter and CategoryHolder and category_row.xml
class CategoryFeed (
val menu_mode : String,
val categories : List<Categories>,
val featured_categories : List<Featured_categories>,
val custom_menu : List<String>
)
class Featured_categories (
val term_id : Int,
val name : String
)
class Categories (
val term_id : Int,
val name : String
)
package com.ahmadnaser.gbeapiswpblogdemo
import android.content.Intent
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.ahmadnaser.gbeapiswpblogdemo.R
class CategoryAdapter(val categoryFeed: CategoryFeed) : RecyclerView.Adapter<CategoryHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.category_row, parent, false)
return CategoryHolder(v)
}
override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
val category = categoryFeed.categories[position]
// holder.categoryTitle.text = category.name
holder.categoryTitle.text = Html.fromHtml(category.name).toString();
holder.imageView_cat.alpha = 0.3f
when (category.name) {
"Beauty" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.blog)
}
"Business" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.cyber_attacks)
}
"Fashion" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.cyber_news)
}
"Hair" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.cyber_security)
}
"Health & Fitness" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.did_you_know)
}
"Life & Love" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.network_security)
}
"Makeup & Skincare" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.new_release)
}
"Photography" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.research)
}
"Relationships" -> {
holder.imageView_cat.setBackgroundResource(R.drawable.tech)
}
}
holder.category = category
}
override fun getItemCount(): Int {
return categoryFeed.categories.count()
}
}
class CategoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var category: Categories? = null
var imageView_cat: ImageView = itemView.findViewById<View>(R.id.imageView_cat) as ImageView
var categoryTitle: TextView = itemView.findViewById<View>(R.id.categoryTitle) as TextView
companion object {
val CATEGORY_ID_KEY = "CATEGORY_ID"
val CATEGORY_TITLE_KEY = "CATEGORY_TITLE"
}
init {
itemView.setOnClickListener {
val intent = Intent(itemView.context, CategoryDetail::class.java)
intent.putExtra(CATEGORY_ID_KEY, category?.term_id)
intent.putExtra(CATEGORY_TITLE_KEY, category?.name)
itemView.context.startActivity(intent)
}
}
}
category_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:id="@+id/cardView_cat"
android:layout_width="0dp"
android:layout_height="120dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:cardCornerRadius="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView_cat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside" />
<TextView
android:id="@+id/categoryTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@+id/imageView_cat"
android:layout_alignLeft="@+id/imageView_cat"
android:layout_alignRight="@+id/imageView_cat"
android:layout_alignTop="@+id/imageView_cat"
android:layout_margin="1dp"
android:gravity="center"
android:text="Hello"
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
15-Call Category Fragment in MainActivity
override fun onTabSelected(position: Int, wasSelected: Boolean): Boolean {
when (position) {
0 -> {
val fragment = HomeFragment.newInstance()
supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit()
}
1 -> {
val fragment = CategoryFragment.newInstance()
supportFragmentManager.beginTransaction().replace(R.id.container, fragment).commit()
}
}
return true
}
16- CategoryDetailActivity, CategoryDetailAdapter, activity_post_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ahmadnaser.gbeapiswpblogdemo.CategoryDetail">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView_catDetail"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
package com.ahmadnaser.gbeapiswpblogdemo
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.text.Html
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.activity_category_detail.*
import okhttp3.*
import java.io.IOException
class CategoryDetail : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_category_detail)
recyclerView_catDetail.layoutManager = LinearLayoutManager(this)
val intent = intent
val navBarTitle = intent.getStringExtra(CategoryHolder.CATEGORY_TITLE_KEY)
supportActionBar?.title = Html.fromHtml(navBarTitle).toString();
val id = intent.getIntExtra(CategoryHolder.CATEGORY_ID_KEY, 0)
//REST API Url
val url = "http://greenapis.com/gbe-api/v1/category/" + id
val request = Request.Builder().url(url).addHeader("ApiKey","****").build()
// val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, LatestPosts::class.java)
runOnUiThread {
recyclerView_catDetail.adapter = CategoryDetailAdapter(homeFeed)
}
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
}
package com.ahmadnaser.gbeapiswpblogdemo
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.view.LayoutInflater
import android.view.ViewGroup
import com.ahmadnaser.gbeapiswpblogdemo.R
import com.squareup.picasso.Picasso
class CategoryDetailAdapter(val homeFeed: LatestPosts) : RecyclerView.Adapter<CustomHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.post_row, parent, false)
return CustomHolder(v)
}
override fun onBindViewHolder(holder: CustomHolder?, position: Int) {
try {
val post = homeFeed.feed.get(position)
holder?.postTitle?.text = Html.fromHtml(post.name)
holder?.authorName?.text = post.timeStamp.toString() + " • " + post.author
Picasso.with(holder?.itemView?.context).load(post.image).into(holder?.thumbnailImageView)
holder?.post = post
}
catch (e: Exception) {
// handler
}
finally {
// optional finally block
}
}
override fun getItemCount(): Int {
return homeFeed.feed.count()
}
}
//class CategoryDetailHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// var post: Post? = null
//
// var postTitle: TextView = itemView.findViewById<View>(R.id.postTitle) as TextView
// var authorName: TextView = itemView.findViewById<View>(R.id.authorName) as TextView
// var thumbnailImageView: ImageView = itemView.findViewById<View>(R.id.postThumbnail) as ImageView
//
// companion object {
//
// val POST_IMAGE_KEY = "POST_IMAGE"
// val POST_TITLE_KEY = "POST_TITLE"
// val POST_AUTHOR_KEY = "POST_AUTHOR"
// val POST_DATE_KEY = "POST_DATE"
// val POST_CONTENT_KEY = "POST_CONTENT"
//
// }
//
// init {
// itemView.setOnClickListener {
// val intent = Intent(itemView.context, PostDetail::class.java)
//
// intent.putExtra(POST_IMAGE_KEY, post?.attachments!![0].url)
// intent.putExtra(POST_TITLE_KEY, post?.title_plain)
// intent.putExtra(POST_AUTHOR_KEY, post?.author?.name)
// intent.putExtra(POST_DATE_KEY, post?.date)
// intent.putExtra(POST_CONTENT_KEY, post?.content)
//
// itemView.context.startActivity(intent)
// }
// }
//}
17- FeedDetails, PostDetailActivity, CategoryDetailAdapter, activity_post_detail.xml
package com.ahmadnaser.gbeapiswpblogdemo
class FeedDetails(
val id : Int,
val name : String,
val image : String,
val thumbnail : String,
val category : String,
val description : String,
val profilePic : String,
val timeStamp : Long,
val url : String,
val load_url : String,
val load_external_view : Boolean,
val author : String,
val can_comment : String,
val comments : Int,
val story_content : String,
val is_protected : Boolean,
val post_password : String,
val urls : List,
val matches : List>,
val images : String)
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.text.Html
import android.view.View
import android.widget.Toast
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_post_detail.*
import org.json.JSONObject
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.AuthFailureError
import com.ahmadnaser.gbeapiswpblogdemo.R
class PostDetail : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post_detail)
// we'll change the nav bar title.
val intent = intent
val navBarTitle = intent.getStringExtra(CustomHolder.POST_TITLE_KEY)
supportActionBar?.title = navBarTitle
val thumbnail = intent.getStringExtra(CustomHolder.POST_IMAGE_KEY)
Picasso.with(this).load(thumbnail).into(postThumbnail)
val title = intent.getStringExtra(CustomHolder.POST_TITLE_KEY)
postTitle.text = Html.fromHtml(title)
val author = intent.getStringExtra(CustomHolder.POST_AUTHOR_KEY)
val date = intent.getStringExtra(CustomHolder.POST_DATE_KEY)
authorName.text = date + " • " + author
// val content = intent.getStringExtra(CustomHolder.POST_CONTENT_KEY)
fetchJsonContent(intent.getIntExtra(CustomHolder.POST_ID,0),"http://greenapis.com/gbe-api/v1/post/")
}
fun fetchJsonContent(postId:Int,URL:String) {
var url = URL + postId
prgsEmployeeUpdate.visibility = View.VISIBLE
val jsonObjReq = object : JsonObjectRequest(Request.Method.GET,
url, null,
Response.Listener<JSONObject> { response ->
try {
prgsEmployeeUpdate.visibility = View.GONE
println(response.toString())
contentBlock!!.settings.loadsImagesAutomatically = true
contentBlock!!.settings.javaScriptEnabled = true
// post_content.setVisibility(View.GONE)
contentBlock.setVisibility(View.VISIBLE)
var post_con = "<!DOCTYPE html>" +
"<html lang=\"ar\">" +
" <head>" +
" <meta charset=\"utf-8\">" +
" </head>" +
" <body>" +
" #content# " +
" </body>" +
"</html>"
try {
val in_s = resources.openRawResource(R.raw.post_format_rtl)
val b = ByteArray(in_s.available())
in_s.read(b)
post_con = String(b)
} catch (e: Exception) {
e.printStackTrace()
}
post_con = post_con.replace("#title#", response.getString("name"))
post_con = post_con.replace("#content#", response.getString("story_content"))
//post_contentHTML.loadData(post_con, "text/html; charset=utf-8", "utf-8");
if (response.has("load_external_view") && response.getBoolean("load_external_view") === java.lang.Boolean.TRUE) {
// contentBlock.loadUrl(response.getString("load_url")) //not webview
} else {
contentBlock.loadDataWithBaseURL(
null,
post_con,
"text/html",
"UTF-8", null)
}
}catch (ex:Exception){
prgsEmployeeUpdate.visibility = View.GONE
Toast.makeText(applicationContext,"Error " + ex.message, Toast.LENGTH_LONG).show()
}
},
Response.ErrorListener {
//Failure Callback
}) {
/** Passing some request headers* */
@Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val headers = HashMap<String, String>()
headers.put("Content-Type", "application/json")
headers.put("ApiKey", "****")
return headers
}
}
GBEAPISDemo.instance?.addToRequestQueue(jsonObjReq)
}
}
activity_post_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.ahmadnaser.gbeapiswpblogdemo.PostDetail">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/postThumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="Post Thumbnail"
android:minHeight="180dp"
android:scaleType="centerCrop"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
android:id="@+id/postTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:text="Post Title"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" />
<TextView
android:id="@+id/authorName"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="Author Name"
android:textAppearance="@style/TextAppearance.Compat.Notification.Info" />
<ProgressBar
android:id="@+id/prgsEmployeeUpdate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foregroundGravity="center_horizontal|center"
android:padding="10dp"
android:visibility="visible" />
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.kautube.test.ikatanalumni.MainActivity"
android:id="@+id/contentBlock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
/>
</LinearLayout>
</ScrollView>
</LinearLayout>
18- raw/post_format.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="text/html; utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>#title#</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<style type="text/css">
body iframe, body img,
body embed, body object{max-width:320px!important;height:auto!important;border:0;}
/**
* 8.0 Alignments
*/
.alignleft {
display: inline;
float: left;
}
.alignright {
display: inline;
float: right;
}
.aligncenter {
display: block;
margin-right: auto;
margin-left: auto;
}
blockquote.alignleft,
.wp-caption.alignleft,
img.alignleft {
margin: 0.4em 1.6em 1.6em 0;
}
blockquote.alignright,
.wp-caption.alignright,
img.alignright {
margin: 0.4em 0 1.6em 1.6em;
}
blockquote.aligncenter,
.wp-caption.aligncenter,
img.aligncenter {
clear: both;
margin-top: 0.4em;
margin-bottom: 1.6em;
}
.wp-caption.alignleft,
.wp-caption.alignright,
.wp-caption.aligncenter {
margin-bottom: 1.2em;
}
.entry-content,
.entry-summary {
padding: 0 7.6923% 7.6923%;
}
.entry-content > :last-child,
.entry-summary > :last-child {
margin-bottom: 0;
}
.entry-content,
.entry-summary,
.page-content,
.comment-content {
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
word-wrap: break-word;
}
.entry-content h1,
.entry-summary h1,
.page-content h1,
.comment-content h1 {
font-size: 26px;
font-size: 2.6rem;
line-height: 1.1538;
margin-top: 1.8462em;
margin-bottom: 0.9231em;
}
.entry-content h2,
.entry-summary h2,
.page-content h2,
.comment-content h2 {
font-size: 22px;
font-size: 2.2rem;
line-height: 1.3636;
margin-top: 2.1818em;
margin-bottom: 1.0909em;
}
.entry-content h3,
.entry-summary h3,
.page-content h3,
.comment-content h3 {
font-size: 18px;
font-size: 1.8rem;
line-height: 1.3333;
margin-top: 2.6667em;
margin-bottom: 1.3333em;
}
.entry-content h4,
.entry-content h5,
.entry-content h6,
.entry-summary h4,
.entry-summary h5,
.entry-summary h6,
.page-content h4,
.page-content h5,
.page-content h6,
.comment-content h4,
.comment-content h5,
.comment-content h6 {
font-size: 15px;
font-size: 1.5rem;
line-height: 1.2;
margin-top: 3.2em;
margin-bottom: 1.6em;
}
.entry-content h5,
.entry-content h6,
.entry-summary h5,
.entry-summary h6,
.page-content h5,
.page-content h6,
.comment-content h5,
.comment-content h6 {
letter-spacing: 0.1em;
text-transform: uppercase;
}
.entry-content > h1:first-child,
.entry-content > h2:first-child,
.entry-content > h3:first-child,
.entry-content > h4:first-child,
.entry-content > h5:first-child,
.entry-content > h6:first-child,
.entry-summary > h1:first-child,
.entry-summary > h2:first-child,
.entry-summary > h3:first-child,
.entry-summary > h4:first-child,
.entry-summary > h5:first-child,
.entry-summary > h6:first-child,
.page-content > h1:first-child,
.page-content > h2:first-child,
.page-content > h3:first-child,
.page-content > h4:first-child,
.page-content > h5:first-child,
.page-content > h6:first-child,
.comment-content > h1:first-child,
.comment-content > h2:first-child,
.comment-content > h3:first-child,
.comment-content > h4:first-child,
.comment-content > h5:first-child,
.comment-content > h6:first-child {
margin-top: 0;
}
.entry-content a,
.entry-summary a,
.page-content a,
.comment-content a,
.pingback .comment-body > a {
border-bottom: 1px solid #333;
}
.entry-content a:hover,
.entry-content a:focus,
.entry-summary a:hover,
.entry-summary a:focus,
.page-content a:hover,
.page-content a:focus,
.comment-content a:hover,
.comment-content a:focus,
.pingback .comment-body > a:hover,
.pingback .comment-body > a:focus {
border-bottom: 0;
}
.entry-content a img,
.entry-summary a img,
.page-content a img,
.comment-content a img {
display: block;
}
.entry-content .more-link,
.entry-summary .more-link:after {
white-space: nowrap;
}
.entry-content .more-link:after,
.entry-summary .more-link:after {
content: "\f429";
font-size: 16px;
position: relative;
top: 5px;
}
</style>
</head>
<body>#content#</body>
</html>
Awesome post, thanks for sharing.