Skip to content

Commit e87adb5

Browse files
committed
Basic table of contents app
0 parents  commit e87adb5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1090
-0
lines changed

Diff for: .gitignore

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/.idea
2+
*.iml
3+
.gradle
4+
/local.properties
5+
/.idea/caches
6+
/.idea/libraries
7+
/.idea/modules.xml
8+
/.idea/workspace.xml
9+
/.idea/navEditor.xml
10+
/.idea/assetWizardSettings.xml
11+
.DS_Store
12+
/build
13+
/app/build
14+
/captures
15+
.externalNativeBuild
16+
.cxx

Diff for: README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Supporting
2+
3+
If you'd like to read the GraphQL Guide, and if you can afford to purchase it or if your company reimburses you for educational materials (most do 👍), we would value your support: [https://graphql.guide](https://graphql.guide).
4+
5+
## Chapter 10: Android
6+
7+
Numbered branches (`0` and up) have the most up-to-date code (often newer than the most recent published revision of the book). When code has changed between book revisions, a new code version is created, and each branch is tagged, with the naming format `[step]_[version]`. For instance, revision `r6` of the book was published with version `0.1.0` of this repo, so the tag for step 5 was [`5_0.1.0`](https://github.com/GraphQLGuide/guide-android/releases/tag/5_0.1.0).
8+
9+
## Contributors
10+
11+
[/GraphQLGuide/guide-android/graphs/contributors](https://github.com/GraphQLGuide/guide-android/graphs/contributors)
12+
13+
Thank you to everyone who has contributed 😃🙌
14+
15+
## Contributing
16+
17+
We welcome issues and PRs! For large changes, we recommend opening an issue first to get feedback before putting in the work of a PR. Minor things like typo fixes can go directly to PRs and will usually get a quick response 😊
18+
19+
When editing code that's part of the book, submit the PR to the corresponding step branch (eg branch `3` for step 3) instead of to `master`.
20+
21+
![Github PR creation](https://res.cloudinary.com/graphql/pr-base.png)
22+
23+
*Setting the base branch to step `3`*
24+
25+
---
26+
27+
[CHANGELOG](https://github.com/GraphQLGuide/guide-android/blob/master/CHANGELOG.md)

Diff for: app/build.gradle.kts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
plugins {
2+
id("com.android.application")
3+
id("kotlin-android")
4+
id("androidx.navigation.safeargs.kotlin")
5+
id("com.apollographql.apollo").version("2.2.2")
6+
}
7+
8+
android {
9+
compileSdkVersion(29)
10+
buildToolsVersion("29.0.3")
11+
12+
defaultConfig {
13+
applicationId = "guide.graphql.toc"
14+
minSdkVersion(23)
15+
targetSdkVersion(29)
16+
versionCode = 1
17+
versionName = "1.0"
18+
}
19+
20+
buildFeatures {
21+
viewBinding = true
22+
}
23+
24+
compileOptions {
25+
sourceCompatibility = JavaVersion.VERSION_1_8
26+
targetCompatibility = JavaVersion.VERSION_1_8
27+
}
28+
}
29+
30+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
31+
kotlinOptions {
32+
jvmTarget = "1.8"
33+
}
34+
}
35+
36+
dependencies {
37+
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.72")
38+
implementation("androidx.appcompat:appcompat:1.1.0")
39+
implementation("androidx.core:core-ktx:1.3.0")
40+
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
41+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
42+
implementation("androidx.recyclerview:recyclerview:1.1.0")
43+
implementation("androidx.navigation:navigation-fragment-ktx:2.3.0")
44+
implementation("androidx.navigation:navigation-ui-ktx:2.3.0")
45+
implementation("com.google.android.material:material:1.1.0")
46+
implementation("com.apollographql.apollo:apollo-runtime:2.2.2")
47+
implementation("com.apollographql.apollo:apollo-coroutines-support:2.2.2")
48+
}
49+
50+
apollo {
51+
generateKotlinModels.set(true)
52+
}

Diff for: app/src/main/AndroidManifest.xml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="guide.graphql.toc">
4+
5+
<uses-permission android:name="android.permission.INTERNET"/>
6+
<application
7+
android:allowBackup="true"
8+
android:icon="@mipmap/ic_launcher"
9+
android:label="@string/app_name"
10+
android:roundIcon="@mipmap/ic_launcher_round"
11+
android:supportsRtl="true"
12+
android:theme="@style/AppTheme">
13+
<activity android:name="guide.graphql.toc.MainActivity">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
<category android:name="android.intent.category.LAUNCHER" />
17+
</intent-filter>
18+
</activity>
19+
</application>
20+
21+
</manifest>
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query Chapters {
2+
chapters {
3+
id
4+
number
5+
title
6+
}
7+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
query Sections($id: Int!) {
2+
chapter(id: $id) {
3+
number
4+
title
5+
sections {
6+
number
7+
title
8+
}
9+
}
10+
}

Diff for: app/src/main/graphql/guide/graphql/toc/schema.json

+1
Large diffs are not rendered by default.

Diff for: app/src/main/java/guide/graphql/toc/Apollo.kt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package guide.graphql.toc
2+
3+
import com.apollographql.apollo.ApolloClient
4+
5+
val apolloClient: ApolloClient = ApolloClient.builder()
6+
.serverUrl("https://api.graphql.guide/graphql")
7+
.build()
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package guide.graphql.toc
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.RecyclerView
6+
import guide.graphql.toc.databinding.ChapterBinding
7+
8+
class ChaptersAdapter(
9+
private val chapters: List<ChaptersQuery.Chapter>
10+
) :
11+
RecyclerView.Adapter<ChaptersAdapter.ViewHolder>() {
12+
13+
class ViewHolder(val binding: ChapterBinding) : RecyclerView.ViewHolder(binding.root)
14+
15+
override fun getItemCount(): Int {
16+
return chapters.size
17+
}
18+
19+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
20+
val binding = ChapterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
21+
return ViewHolder(binding)
22+
}
23+
24+
var onItemClicked: ((ChaptersQuery.Chapter) -> Unit)? = null
25+
26+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
27+
val chapter = chapters[position]
28+
val header =
29+
if (chapter.number == null) chapter.title else "Chapter ${chapter.number.toInt()}"
30+
31+
holder.binding.chapterHeader.text = header
32+
holder.binding.chapterSubheader.text = if (chapter.number == null) "" else chapter.title
33+
34+
holder.binding.root.setOnClickListener {
35+
onItemClicked?.invoke(chapter)
36+
}
37+
}
38+
}
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package guide.graphql.toc
2+
3+
import android.os.Bundle
4+
import android.util.Log
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import androidx.fragment.app.Fragment
9+
import androidx.lifecycle.lifecycleScope
10+
import androidx.navigation.fragment.findNavController
11+
import androidx.recyclerview.widget.LinearLayoutManager
12+
import com.apollographql.apollo.coroutines.toDeferred
13+
import com.apollographql.apollo.exception.ApolloException
14+
import guide.graphql.toc.databinding.ChaptersFragmentBinding
15+
16+
class ChaptersFragment : Fragment() {
17+
private lateinit var binding: ChaptersFragmentBinding
18+
19+
override fun onCreateView(
20+
inflater: LayoutInflater,
21+
container: ViewGroup?,
22+
savedInstanceState: Bundle?
23+
): View? {
24+
binding = ChaptersFragmentBinding.inflate(inflater)
25+
return binding.root
26+
}
27+
28+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
29+
super.onViewCreated(view, savedInstanceState)
30+
31+
lifecycleScope.launchWhenResumed {
32+
val response = try {
33+
apolloClient.query(
34+
ChaptersQuery()
35+
).toDeferred().await()
36+
} catch (e: ApolloException) {
37+
Log.d("ChaptersQuery", "GraphQL request failed", e)
38+
return@launchWhenResumed
39+
}
40+
41+
val chapters = response.data?.chapters
42+
if (chapters == null || response.hasErrors()) {
43+
return@launchWhenResumed
44+
}
45+
46+
val adapter =
47+
ChaptersAdapter(chapters)
48+
binding.chapters.layoutManager = LinearLayoutManager(requireContext())
49+
binding.chapters.adapter = adapter
50+
51+
adapter.onItemClicked = { chapter ->
52+
findNavController().navigate(
53+
ChaptersFragmentDirections.viewSections(
54+
chapterId = chapter.id
55+
)
56+
)
57+
}
58+
}
59+
}
60+
}

Diff for: app/src/main/java/guide/graphql/toc/MainActivity.kt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package guide.graphql.toc
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
6+
class MainActivity : AppCompatActivity() {
7+
8+
override fun onCreate(savedInstanceState: Bundle?) {
9+
super.onCreate(savedInstanceState)
10+
11+
setContentView(R.layout.activity_main)
12+
}
13+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package guide.graphql.toc
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.RecyclerView
6+
import guide.graphql.toc.databinding.SectionBinding
7+
8+
class SectionsAdapter(
9+
private val chapterNumber: Int?,
10+
private val sections: List<SectionsQuery.Section>
11+
) :
12+
RecyclerView.Adapter<SectionsAdapter.ViewHolder>() {
13+
14+
class ViewHolder(val binding: SectionBinding) : RecyclerView.ViewHolder(binding.root)
15+
16+
override fun getItemCount(): Int {
17+
return sections.size
18+
}
19+
20+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
21+
val binding = SectionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
22+
return ViewHolder(binding)
23+
}
24+
25+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
26+
val section = sections[position]
27+
holder.binding.sectionTitle.text =
28+
if (section.number == null) section.title else "${chapterNumber}.${section.number}: ${section.title}"
29+
}
30+
}
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package guide.graphql.toc
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.fragment.app.Fragment
8+
import androidx.lifecycle.lifecycleScope
9+
import androidx.navigation.fragment.navArgs
10+
import androidx.recyclerview.widget.LinearLayoutManager
11+
import com.apollographql.apollo.coroutines.toDeferred
12+
import com.apollographql.apollo.exception.ApolloException
13+
import guide.graphql.toc.databinding.SectionsFragmentBinding
14+
15+
class SectionsFragment : Fragment() {
16+
17+
private lateinit var binding: SectionsFragmentBinding
18+
val args: SectionsFragmentArgs by navArgs()
19+
20+
override fun onCreateView(
21+
inflater: LayoutInflater,
22+
container: ViewGroup?,
23+
savedInstanceState: Bundle?
24+
): View? {
25+
binding = SectionsFragmentBinding.inflate(inflater)
26+
return binding.root
27+
}
28+
29+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30+
super.onViewCreated(view, savedInstanceState)
31+
32+
lifecycleScope.launchWhenResumed {
33+
binding.spinner.visibility = View.VISIBLE
34+
binding.error.visibility = View.GONE
35+
36+
val response = try {
37+
apolloClient.query(
38+
SectionsQuery(id = args.chapterId)
39+
).toDeferred().await()
40+
} catch (e: ApolloException) {
41+
binding.spinner.visibility = View.GONE
42+
binding.error.text = "Error making the GraphQL request: ${e.message}"
43+
binding.error.visibility = View.VISIBLE
44+
return@launchWhenResumed
45+
}
46+
47+
val chapter = response.data?.chapter
48+
if (chapter == null || response.hasErrors()) {
49+
binding.spinner.visibility = View.GONE
50+
binding.error.text = response.errors?.get(0)?.message
51+
binding.error.visibility = View.VISIBLE
52+
return@launchWhenResumed
53+
}
54+
55+
val chapterNumber = chapter.number?.toInt()
56+
binding.spinner.visibility = View.GONE
57+
binding.chapterHeader.title =
58+
if (chapter.number == null) chapter.title else "Chapter ${chapterNumber}: ${chapter.title}"
59+
60+
if (chapter.sections.size > 1) {
61+
val adapter =
62+
SectionsAdapter(chapterNumber, chapter.sections as List<SectionsQuery.Section>)
63+
binding.sections.layoutManager = LinearLayoutManager(requireContext())
64+
binding.sections.adapter = adapter
65+
} else {
66+
binding.error.text = "This chapter has no sections."
67+
binding.error.visibility = View.VISIBLE
68+
}
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)