nyt-android-workshop

所属分类:android开发
开发工具:kotlin
文件大小:0KB
下载次数:0
上传日期:2019-07-17 17:19:46
上 传 者sh-1993
说明:  2019年创客周Android开发研讨会,
(Maker Week 2019 Android Development Workshop,)

文件列表:
.idea/ (0, 2019-07-17)
.idea/modules.xml (292, 2019-07-17)
.idea/nyt-android-workshop.iml (336, 2019-07-17)
.idea/vcs.xml (167, 2019-07-17)
.idea/workspace.xml (11857, 2019-07-17)
Completed Project - Day 1/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2.zip (136542, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/codeStyles/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/codeStyles/Project.xml (381, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/codeStyles/codeStyleConfig.xml (142, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/encodings.xml (135, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/gradle.xml (626, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/misc.xml (357, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/.idea/runConfigurations.xml (564, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/build.gradle (1250, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/proguard-rules.pro (751, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/java/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/java/com/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/java/com/example/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/java/com/example/nytimesmini/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/androidTest/java/com/example/nytimesmini/ExampleInstrumentedTest.kt (634, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/AndroidManifest.xml (883, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/nytimesmini/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/nytimesmini/ArticleActivity.kt (451, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/nytimesmini/MainActivity.kt (2535, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/nytimesmini/NewsAdapter.kt (1596, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/java/com/example/nytimesmini/NewsStory.kt (207, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/res/ (0, 2019-07-17)
Completed Project - Day 1/NYTimesMini2/app/src/main/res/drawable-v24/ (0, 2019-07-17)
... ...

# Android Development Workshop - Monday 2 - 4 p.m.: Intro to Android Development - 15W2-115 - [Zip of the completed project](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/Completed%20Project%20-%20Day%201/NYTimesMini2.zip) - Tuesday 2 - 4 p.m.: Kotlin for Android Development - 15W2-115 - [Zip of the completed project](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/Completed%20Project%20-%20Day%202/NYTimesMini2.zip) - Wednesday 2 - 4 p.m.: Advanced Android - 12E2-140 *Office hours from 4 - 5 pm each day.* ## The Goal Build a NYTimes app using the [public API](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/https://developer.nytimes.com/): https://developer.nytimes.com/ ## Creating a project - Open Android Studio - "Start a new Android Studio project" - "Empty Activity" ## Configure the project ![inline](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/images/configure-the-project.png) ## Getting around Android Studio - Project panel - Emulator - Run button ## Enable developer options on your device! - For participants with Android devices, go to: ```Settings > About phone > Build Number``` - Tap build number seven times. You’ll see a message saying “You are now a developer!” - When you connect your device to your laptop, click **OK** on the alert that pops up. ## Edit the layout - Open activity_main.xml - Find the text that says "Hello World!" - Replace it with your own message and run the app ## Displaying a list - From activity_main.xml, open the "Design" tab - Delete the `TextView` - Drag to add a `RecyclerView` - Give it an id: `recyclerView` ## Creating an adapter In a new file NewsAdapter.kt ```kotlin class NewsAdapter : RecyclerView.Adapter() { class NewsViewHolder(val headline: TextView) : RecyclerView.ViewHolder(headline) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder { // Called when a new, empty view is created } override fun getItemCount(): Int { // Tells the recycler view how many items to display } override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { // Called when a view is about to be scrolled on to the screen } } ``` ## Create a viewholder with a view to display the headline In NewsAdapter.kt ```kotlin override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder { // Called when a new, empty view is created val headlineTextView = TextView(parent.context) return NewsViewHolder(headlineTextView) } ``` ## Create a list of data to bind with the adapter In NewsAdapter.kt ```kotlin class NewsAdapter : RecyclerView.Adapter() { var news: List = ArrayList() //... } ``` ## We want to display as many headline items as there are news items In NewsAdapter.kt ```kotlin override fun getItemCount(): Int { // Tells the recycler view how many items to display return news.size } ``` ## When an item is scrolled onto the screen, show the headline In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { // Called when a view is about to be scrolled on to the screen holder.headline.text = news[position] } ``` ## Setting up the RecyclerView In MainActivity.kt ```kotlin lateinit var recyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = NewsAdapter() } ``` ## Allow the news to be set from outside of the adapter In NewsAdapter.kt ```kotlin var news: List = emptyList() fun updateNews(news: List) { this.news = news notifyDataSetChanged() } ``` In MainActivity.kt ```kotlin val adapter = NewsAdapter() val news = listOf( "Crocodiles Went Through a Vegetarian Phase, Too", "This Cockatoo Thinks He Can Dance", "The Moon Is a Hazardous Place to Live") override fun onCreate(savedInstanceState: Bundle?) { // ... recyclerView.adapter = adapter adapter.updateNews(news) } ``` ## Make a nicer layout - Create a new layout file `item_news.xml` - Add an ImageView and two TextViews and give them `id` values - Adjust the constraints and margins to align the views - Give the ImageView a placeholder background ![70% right](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/images/nicer-layout.png) ## Inflate the layout in the adapter In NewsAdapter.kt ```kotlin class NewsAdapter(val context: Context) : RecyclerView.Adapter() { class NewsViewHolder(view: View) : RecyclerView.ViewHolder(view) val inflater = LayoutInflater.from(context) //... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder { // Called when a new, empty view is created val view = inflater.inflate(R.layout.item_news, parent, false) return NewsViewHolder(view) } ``` ## Bind the data to the new layout ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { // Called when a view is about to be scrolled on to the screen holder.itemView.headline.text = news[position] } ``` ## Create a data class to represent a news story Create a new file NewsStory.kt ```kotlin data class NewsStory( val headline: String, val summary: String, val imageUrl: String, val clickUrl: String ) ``` ## Update the adapter to handle the new data class In NewsAdapter.kt ```kotlin var news: List = ArrayList() fun updateNews(news: List) { this.news = news notifyDataSetChanged() } ``` ## Bind the new data to the new layout In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { // Called when a view is about to be scrolled on to the screen holder.itemView.headline.text = news[position].headline holder.itemView.summary.text = news[position].summary } ``` ## Update the data being passed to the adapter In MainActivity.kt ```kotlin override fun onCreate(savedInstanceState: Bundle?) { //... updateNews() } fun updateNews() { val news = listOf( NewsStory( headline = "Crocodiles Went Through a Vegetarian Phase, Too", summary = "Ancestors of modern crocodiles evolved to survive on a plant diet at least three times, researchers say.", imageUrl = "https://static01.nyt.com/images/2019/07/09/science/27TB-VEGGIECROC1/27TB-VEGGIECROC1-videoLarge.jpg", clickUrl = "https://www.nytimes.com/2019/06/27/science/crocodiles-vegetarian-teeth.html"), NewsStory( headline = "This Cockatoo Thinks He Can Dance", summary = "Researchers have become convinced that Snowball, a YouTube sensation, and perhaps other animals, share humans’ sensitivity to music.", imageUrl = "https://static01.nyt.com/images/2019/07/09/autossell/Screen-Shot-2019-07-09-at-1/Screen-Shot-2019-07-09-at-1-videoLarge.jpg", clickUrl = "https://www.nytimes.com/2019/07/09/science/cockatoo-snowball-dance.html"), NewsStory( headline = "The Moon Is a Hazardous Place to Live", summary = "If we get back to the lunar surface, astronauts will have to contend with much more than perilous rocket flights and the vacuum of space.", imageUrl = "https://static01.nyt.com/images/2019/07/14/science/14MOONHAZARDS4/14MOONHAZARDS4-mediumThreeByTwo440-v2.jpg", clickUrl = "https://www.nytimes.com/2019/07/08/science/apollo-moon-colony-dangers.html") ) adapter.updateNews(news) } ``` ## Load the image URL in the ImageView We'll use a third-party image loading library called [Picasso](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/https://square.github.io/picasso/). ## Add the Picasso dependency In app/build.gradle ```groovy dependencies { //... implementation 'com.squareup.picasso:picasso:2.71828' } ``` Then do a Gradle sync. ## Add internet permission In AndroidManifest.xml ```xml //... ``` ## Load the image URL in the ImageView In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { //... Picasso.get().load(news[position].imageUrl).into(holder.itemView.imageView) } ``` ## Make a toast when a story is clicked In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { //... holder.itemView.setOnClickListener { Toast.makeText(context, news[position].headline, Toast.LENGTH_LONG).show() } } ``` ## Create a new activity to show a story * Create a new "Empty Activity" called StoryActivity * Add a WebView to `activity_story.xml` ## Navigate to the activity when a story is clicked In NewsAdapter.kt ```kotlin holder.itemView.setOnClickListener { val intent = Intent(context, StoryActivity::class.java) intent.putExtra("url", news[position].clickUrl) context.startActivity(intent) } ``` ## Load the URL in the WebView In StoryActivity.kt ```kotlin override fun onCreate(savedInstanceState: Bundle?) { //.. val url = intent.extras["url"] as String storyWebView.loadUrl(url) } ``` # Day 2 ## Let's get some real data! We want to... * Make a network request to the NYTimes Public API * Get back a list of items ranked in Top Stories * Parse these items to display in our RecyclerView ## Some tools we'll use * OkHttp - HTTP client for Android/Java/Kotlin. * Retrofit - adapts REST interfaces so they can be treated like callable Java/Kotlin objects. * Moshi - parses JSON in Java/Kotlin data objects. * Coroutines - Kotlin language-level support for writing asynchronous code. ## Add the kapt plugin In app/build.gradle ```groovy apply plugin: 'kotlin-kapt' ``` ## Add the dependencies In app/build.gradle ```groovy dependencies { //... def okhttp3_version = "3.12.0" def retrofit2_version = "2.5.0" def moshiVersion="1.8.0" def kotlinCoroutineVersion = "1.0.1" // OkHttp implementation "com.squareup.okhttp3:okhttp:$okhttp3_version" implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' // Retrofit implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" implementation "com.squareup.retrofit2:converter-moshi:$retrofit2_version" implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2" // Moshi implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutineVersion" } ``` ## Add the API key in a file that is not checked in to source control In local.properties ```groovy //... apikey="paste the api key here" ``` ## Compile the key into the project In app/build.gradle ```groovy def localProperties = new Properties() localProperties.load(new FileInputStream(rootProject.file("local.properties"))) android { //... defaultConfig { //... buildConfigField "String", "API_KEY", localProperties['apiKey'] } } ``` ## Create Kotlin data classes to represent the API response Create a new class Section.kt ```kotlin @JsonClass(generateAdapter = true) data class Section(val results: List) @JsonClass(generateAdapter = true) data class Result(val title: String, val abstract: String, val url: String, val multimedia: List) @JsonClass(generateAdapter = true) data class Multimedia(val format: String, val url: String) ``` ## Create a Kotlin interface to represent the Top Stories API Create a new class TopStoriesApi.kt ```kotlin interface TopStoriesApi { @GET("/svc/topstories/v2/{section}.json") fun getSection( @Path("section") section: String, @Query("api-key") apiKey: String ): Deferred> } ``` ## Create a singleton factory to provide an instance of TopStoriesApi In TopStoriesApi.kt ```kotlin object ApiFactory { fun retrofit(): Retrofit = Retrofit.Builder() .client(OkHttpClient().newBuilder().build()) .baseUrl("https://api.nytimes.com") .addConverterFactory(MoshiConverterFactory.create()) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() val topStoriesApi: TopStoriesApi = retrofit().create(TopStoriesApi::class.java) } ``` ## Launch a coroutine to make the post request In MainActivity.kt ```kotlin fun updateNews() { val service = ApiFactory.topStoriesApi GlobalScope.launch(Dispatchers.Main) { val postRequest = service.getSection("science", BuildConfig.API_KEY) } } ``` ## Execute the request and show an error if something goes wrong In MainActivity.kt ```kotlin val postRequest = service.getSection("science", BuildConfig.API_KEY) try { val response = postRequest.await() } catch (e: Exception) { Toast.makeText(baseContext, "Something went wrong.", Toast.LENGTH_LONG).show() } ``` ## Convert the data from the response so that it can be used by the NewsAdapter In MainActivity.kt ```kotlin val response = postRequest.await() response.body()?.let { section -> val news = section.results.map { result -> NewsStory(headline = result.title, summary = result.abstract, imageUrl = result.multimedia.firstOrNull { it.format == "superJumbo" }?.url, clickUrl = result.url) } adapter.updateNews(news) } ``` ## Add multiple sections - Add a Tab Layout - Make a list of sections we want to display - When a user clicks a tab, show that section ![right 80%](https://github.com/ramonaharrison/nyt-android-workshop/blob/master/images/sections.png) ## Add a Tab Layout In activity_main.xml ```xml ``` ## Make a list of sections In MainActivity.kt ```kotlin val sections = mapOf( "Home" to "home", "Opinion" to "opinion", "Food" to "food", "Science" to "science", "Travel" to "travel" ) ``` ## Setup the Tab Layout In MainActivity.kt ```kotlin // Called from onCreate() fun setupSections() { for (key in sections.keys) { tabLayout.addTab(tabLayout.newTab().setText(key)) } } ``` ## Add a tab selected listener In MainActivity.kt ```kotlin fun setupSections() { //... tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { updateNews(sections[tab.text]!!) recyclerView.scrollToPosition(0) } override fun onTabReselected(p0: TabLayout.Tab) { // Do nothing. } override fun onTabUnselected(p0: TabLayout.Tab) { // Do nothing. } }) } ``` ## Load the right section In MainActivity.kt ```kotlin fun updateNews(path: String) { val service = ApiFactory.topStoriesApi GlobalScope.launch(Dispatchers.Main) { val postRequest = service.getSection(path, BuildConfig.API_KEY) //... } ``` # Day 3 ## Change the app colors In colors.xml ```xml #FFFFFF #D3D3D3 #FFD300 ``` ## Add a save and share icon to each item In item_news.xml, below the summary `TextView` ```kotlin ``` ## Refactor `onBindViewHolder` In NewsAdapter.kt, clean up the `onBindViewHolder()` function ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { val newsStory = news[position] holder.itemView.headline.text = newsStory.headline holder.itemView.summary.text = newsStory.summary Picasso.get().load(newsStory.imageUrl).into(holder.itemView.imageView) holder.itemView.setOnClickListener { val intent = Intent(holder.itemView.context, ArticleActivity::class.java) intent.putExtra("url", newsStory.clickUrl) holder.itemView.context.startActivity(intent) } } ``` ## Add a click listener for the Share button In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { //... holder.itemView.shareButton.setOnClickListener { val url = newsStory.clickUrl val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, "Check out this NYTimes article: $url") type = "text/plain" } holder.itemView.context.startActivity(Intent.createChooser(sendIntent, "Share this article to:")) } } ``` ## Add a click listener for the Save button In NewsAdapter.kt ```kotlin override fun onBindViewHolder(holder: NewsViewHolder, position: Int) { holder.itemView.saveButton.setOnClickListener { holder.itemView.save ... ...

近期下载者

相关文件


收藏者