kotlin 自定义内联_与Kotlin建立自定义社交网络/时间线提要

kotlin 自定义内联

In this post, we’ll create a simple social network, called “The Stream”, that allows a user to post messages to followers.

在本文中,我们将创建一个简单的社交网络,称为“流”,该网络允许用户向关注者发布消息。

The app will allow a user to post a message to their followers. Stream’s Activity Feed API, combined with Android, makes it straightforward to build this sort of complex interaction. All source code for this application is available on GitHub. This application is fully functional on Android.

该应用程序将允许用户向其关注者发布消息。 Stream的Activity Feed API与Android相结合,使构建这种复杂的交互变得非常简单。 GitHub上提供了此应用程序的所有源代码。 此应用程序在Android上具有完整功能。

Often there is the context around those code snippets, which are essential, such as layout or navigation. Please refer to the full source if you’re confused about how something works, what libraries and versions are used, or how we got to a screen. Each snippet will be accompanied by a comment explaining which file and line it came from.

这些代码段周围通常都有上下文,这是必不可少的,例如布局或导航。 如果您对某些东西的工作方式,使用的库和版本,或如何进入屏幕感到困惑,请参阅完整的源代码。 每个摘录都将附有注释,说明它来自哪个文件和行。

建立“溪流” (Building “The Stream”)

To build our social network, we’ll need both a backend and a mobile application. Most of the work is done in the mobile app, but we need the backend to create frontend tokens for interacting with the Stream API securely.

要构建我们的社交网络,我们需要后端和移动应用程序。 大多数工作都在移动应用程序中完成,但是我们需要后端来创建前端令牌,以便与Stream API安全地进行交互。

For the backend, we’ll rely on Express ( Node.js) leveraging Stream Feed’s JavaScript library.

对于后端,我们将利用Stream FeedJavaScript库依靠Express ( Node.js )。

For the frontend, we’ll build it with Kotlin wrapping Stream Feed’s Java library.

对于前端,我们将使用Kotlin包装Stream Feed的Java库来构建它。

There are two main actions a user takes, posting, and viewing messages. To post a message, the mobile application goes through his flow:

用户执行两个主要操作,即发布和查看消息。 要发布消息,移动应用程序将按照其流程进行操作:

  • User types their name into our mobile application to log in.

    用户在我们的移动应用程序中输入他们的姓名以登录。
  • The Android app registers the user with our backend and receives a Stream Activity Feed frontend token.

    Android应用程序将用户注册到我们的后端,并接收Stream Activity Feed 前端令牌

  • User types in their message and hits “Post”. The mobile app uses the Stream token to create a Stream activity on their user feed via Stream's REST API using the Java library.

    用户键入他们的消息,然后单击“发布”。 该移动应用使用Stream令牌使用Java库通过Stream的REST API在其user供稿上创建Stream活动。

  • User views their posts. The mobile app does this by retrieving its user feed via Stream.

    用户查看他们的帖子。 移动应用程序通过Stream检索其用户供稿。

Here’s what happens if another user wants to follow a user and view their messages:

如果另一个用户想要关注某个用户并查看其消息,则会发生以下情况:

  • User logs in.

    用户登录。
  • User navigates to the user list and selects a user to follow. The mobile app communicates with Stream API to create a follower relationship on their timeline feed.

    用户导航到用户列表,然后选择要关注的用户。 移动应用程序与Stream API通信以在其timeline供稿上创建关注者关系

  • User views their timeline. The mobile app uses Stream API to retrieve their timeline feed, which is composed of all the messages from who they follow.

    用户查看他们的时间表。 该移动应用程序使用Stream API检索其timeline供稿,该供稿由他们关注的所有消息组成。

先决条件 (Prerequisites)

Basic knowledge of Node.js (JavaScript) and Android (Kotlin) is required to follow this tutorial. This code is intended to run locally on your machine.

要学习本教程,需要具备Node.js(JavaScript)和Android(Kotlin)的基础知识。 该代码旨在在您的计算机上本地运行。

If you’d like to follow along, you’ll need an account with Stream. Please make sure you can build a simple Android app before embarking on this tutorial. If you haven’t done so, make sure you have Android Studio installed.

如果您想继续学习,则需要使用Stream帐户。 在开始学习本教程之前,请确保您可以构建一个简单的Android应用 。 如果尚未这样做,请确保已安装Android Studio

Once you have an account with Stream, you need to set up a development app. This is done from your dashboard:

一旦拥有Stream帐户,就需要设置一个开发应用程序。 这是通过您的仪表板完成的:

Image for post

You’ll need to add the credentials from the Stream app to the backend .env file and start the server for the mobile application to work. See the backend README.md for more information.

您需要将Stream应用程序中的凭据添加到backend .env文件,并启动服务器以使移动应用程序正常工作。 有关更多信息,请参见backend README.md

Let’s get to building.

让我们开始构建。

用户发布消息 (User Posts a Message)

We’ll start with a user posting a message.

我们将从用户发布消息开始。

步骤1:登入 (Step 1: Log In)

To communicate with the Stream API, we need a secure frontend token that allows our mobile application to authenticate with Stream directly. This avoids having to proxy all calls through the backend (which is another way of building your application). To do this, we’ll need a backend endpoint that uses our Stream account secrets to generate this token. Once we have this token, we don’t need the backend to do anything else, since the mobile app has access to the Stream API limited by that user’s permissions.

为了与Stream API通信,我们需要一个安全的前端令牌,该令牌允许我们的移动应用程序直接向Stream进行身份验证。 这样避免了必须通过后端代理所有调用(这是构建应用程序的另一种方式)。 为此,我们需要一个后端端点,该端点使用我们的Stream帐户密码来生成此令牌。 有了此令牌后,我们就不需要后端执行任何其他操作,因为移动应用程序可以访问受该用户权限限制的Stream API。

First, we’ll be building the login screen which looks like:

首先,我们将构建登录屏幕,如下所示:

Image for post

To start, let’s lay our form out in Android. In our activity_main.xml layout, we have a simple ConstraintLayout with an EditText and Button:

首先,让我们在Android中布置表格。 在activity_main.xml布局中,我们有一个简单的ConstraintLayout其中包含EditTextButton

<!-- android/app/src/main/res/layout/activity_main.xml:1 -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
                                                   tools:context="io.getstream.thestream.MainActivity">


    <EditText
        android:id="@+id/user"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:autofillHints="Username"
        android:ems="10"
        android:hint="Username"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toStartOf="@+id/submit"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="HardcodedText"/>


    <Button
        android:id="@+id/submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="Login"
        app:layout_constraintBaseline_toBaselineOf="@+id/user"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/user"
        tools:ignore="HardcodedText"/>


</androidx.constraintlayout.widget.ConstraintLayout>

Let’s bind to this layout and respond in MainActivity:

让我们绑定到此布局并在MainActivity响应:

// android/app/src/main/java/io/getstream/thestream/MainActivity.kt:16
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        val submit: Button = findViewById(R.id.submit)
        val userView: EditText = findViewById(R.id.user)


        submit.setOnClickListener {
            val user: String = userView.text.toString()


            launch(Dispatchers.IO) {
                BackendService.signIn(user)


                val feedCredentials = BackendService.getFeedCredentials()


                launch(Dispatchers.Main) {
                    FeedService.init(user, feedCredentials)


                    startActivity(
                        Intent(applicationContext, AuthedMainActivity::class.java)
                    )
                }
            }
        }
    }
}

Note: The asynchronous approach in this tutorial is not necessarily the best or most robust approach. It’s merely a straightforward way to show async interactions without cluttering the code too much. Please research and pick the best asynchronous solution for your application.

注意 :本教程中的异步方法不一定是最佳或最可靠的方法。 这只是显示异步交互而又不会使代码过于混乱的一种直接方法。 请研究并为您的应用选择最佳的异步解决方案。

Here we bind to our button and user input. We listen to the submit button and sign in to our backend. Since this work is making network calls, we need to do this asynchronously. We use Kotlin’s coroutines to accomplish this by binding to the MainScope. We dispatch our sign-in code which tells our BackendService to perform two tasks, sign in to the backend and get the feed frontend credentials. We'll look at how the BackendService accomplishes this in a second.

在这里,我们绑定到按钮和用户输入。 我们听“提交”按钮并登录到我们的后端。 由于这项工作正在进行网络呼叫,因此我们需要异步进行此操作。 我们使用Kotlin的协程通过绑定到MainScope来完成此操作 。 我们分派登录代码,该代码告诉BackendService执行两项任务,登录到后端并获取Feed前端凭据。 我们将在稍后查看BackendService如何完成此任务。

Once we have our tokens, we initialize our FeedService so we can talk to Stream's API (we'll see this in a second as well). When the user is fully authed, and we have our credentials, we start a new activity called AuthedMainActivity, which is the rest of the application.

获得令牌后,我们将初始化FeedService以便我们可以与Stream的API对话(我们还将在稍后看到)。 在对用户进行完全身份验证并获得凭据之后,我们将启动一个名为AuthedMainActivity的新活动,该活动就是应用程序的其余部分。

Before seeing how we post a message, let’s see how we auth and initialize the Stream Feed. First, we sign in to the backend via BackendService.signIn:

在查看我们如何发布消息之前,让我们了解如何对流Feed进行身份验证和初始化。 首先,我们通过BackendService.signIn登录到后端:

// android/app/src/main/java/io/getstream/thestream/services/BackendService.kt:18
fun signIn(user: String) {
    authToken = post(
        "/v1/users",
        mapOf("user" to user)
    )
        .getString("authToken")
    this.user = user
}


// ...


private fun post(path: String, body: Map<String, Any>, authToken: String? = null): JSONObject {
    val request = Request.Builder()
        .url("$apiRoot${path}")
        .post(JSONObject(body).toString().toRequestBody(JSON))


    if (authToken != null) {
        request.addHeader("Authorization", "Bearer $authToken")
    }


    http.newCall(request.build()).execute().use {
        return JSONObject(it.body!!.string())
    }
}

We make a simple POST HTTP request to our backend endpoint /v1/users, which returns a backend authToken that allows the mobile application to make further requests against the backend. Since this not a real implementation of auth, we'll skip the backend code. Please refer to the source if you're curious. Also, keep in mind, this token is not the Stream token. We need to make another call for that.

我们向后端端点/v1/users发出一个简单的POST HTTP请求,该请求返回一个backend authToken ,该authToken允许移动应用程序对该后端发出进一步的请求。 由于这不是真正的auth实现,因此我们将跳过后端代码。 如果您有好奇心,请参阅资料来源。 另外,请记住,此令牌不是 Stream令牌。 我们需要为此再次呼吁。

Once the user is signed in with our backend we can get our feed credentials via BackendService.getFeedCredentials():

当用户使用我们的backend登录后,我们可以通过BackendService.getFeedCredentials()获得我们的供稿凭据:

// android/app/src/main/java/io/getstream/thestream/services/BackendService.kt:27
data class StreamCredentials(val token: String, val apiKey: String)


fun getFeedCredentials(): StreamCredentials {
    val response = post(
        "/v1/stream-feed-credentials",
        mapOf(),
        authToken
    )


    return StreamCredentials(
        response.getString("token"),
        response.getString("apiKey")
    )
}
// backend/src/controllers/v1/stream-feed-credentials/stream-feed-credentials.action.js:1
import dotenv from 'dotenv';
import stream from "getstream";


dotenv.config();


exports.streamFeedCredentials = async (req, res) => {
  try {
    const apiKey = process.env.STREAM_API_KEY;
    const apiSecret = process.env.STREAM_API_SECRET;
    const appId = process.env.STREAM_APP_ID;


    const client = stream.connect(apiKey, apiSecret, appId);


    await client.user(req.user).getOrCreate({ name: req.user });
    const token = client.createUserToken(req.user);


    res.status(200).json({ token, apiKey, appId });
  } catch (error) {
    console.log(error);
    res.status(500).json({ error: error.message });
  }
};

We use the Stream JavaScript library to create a user (if they don’t exist) and generate a Stream frontend token. We return this token, alongside some API information, back to the Android app.

我们使用Stream JavaScript库创建用户(如果不存在)并生成Stream前端令牌 。 我们将此令牌以及一些API信息返回给Android应用。

In the mobile app, we use the returned credentials to initialize our FeedService by calling FeedService.init in MainActivity. Here's the init:

在移动应用中,我们使用返回的凭据通过在MainActivity调用FeedService.init来初始化FeedService 。 这是init

// android/app/src/main/java/io/getstream/thestream/services/FeedService.kt:8
object FeedService {
    private lateinit var client: CloudClient
    private lateinit var user: String


    fun init(user: String, credentials: BackendService.StreamCredentials) {
        this.user = user
        client = CloudClient
            .builder(credentials.apiKey, credentials.token, user)
            .build()
    }


    // ...
}

The FeedService is a singleton (by using Kotlin's object) which stores a CloudClient instance. CloudClient is a class provided by Stream's Java library. This class is specifically used to provide the functionality to client applications via frontend tokens. Stream's Java library contains a normal client for backend applications, so don't get confused about which client to use. The normal client requires private credentials that you don't want to embed in your mobile application.

FeedService是一个单例(通过使用Kotlin的object ),用于存储CloudClient实例。 CloudClient是Stream的Java库提供的类。 此类专门用于通过前端令牌向客户端应用程序提供功能。 Stream的Java库包含用于后端应用程序的普通客户端,因此请不要对要使用哪个客户端感到困惑。 普通客户端需要您不想嵌入到移动应用程序中的私人凭据。

Now that we’re authenticated with Stream, we’re ready to post our first message.

现在我们已经通过Stream进行了身份验证,我们准备发布我们的第一条消息。

步骤2:发布讯息 (Step 2: Posting a Message)

Now we’ll build the form to post a status message to our Stream activity feed. We won’t dive into navigation and layout in this tutorial. Please refer to the source if you’re curious about how we get to this screen or skip ahead to Step 3. We’ll need to build a form that takes what the user wants to say to their followers and submit that to Stream. We use an activity called CreatePostActivity to handle new posts:

现在,我们将构建表单以将状态消息发布到我们的Stream活动供稿中。 在本教程中,我们不会深入探讨导航和布局。 如果您对如何进入此屏幕感到好奇,请参阅 ,或者跳到步骤3。我们需要构建一个表单,将用户想要对他们的关注者说的话提交给Stream。 我们使用一个称为CreatePostActivity的活动来处理新帖子:

// android/app/src/main/java/io/getstream/thestream/CreatePostActivity.kt:13
const val POST_SUCCESS = 99


class CreatePostActivity : AppCompatActivity(), CoroutineScope by MainScope() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_create_post)


        val submit: Button = findViewById(R.id.submit)
        val postView: EditText = findViewById(R.id.post_text)


        submit.setOnClickListener {
            val text: String = postView.text.toString()


            launch(Dispatchers.IO) {
                FeedService.post(text)


                launch(Dispatchers.Main) {
                    setResult(POST_SUCCESS)
                    finish()
                }
            }
        }
    }
}

With the layout:

使用布局:

<!-- android/app/src/main/res/layout/activity_create_post.xml:1 -->
<?xml version="1.0" encoding="utf-8"?>


<androidx.constraintlayout.widget.ConstraintLayout 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"
                                                   tools:context="io.getstream.thestream.MainActivity">


    <EditText
        android:id="@+id/post_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:autofillHints="Enter Post..."
        android:ems="10"
        android:hint="Post Text"
        android:inputType="text"
        app:layout_constraintEnd_toStartOf="@+id/submit"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>


    <Button
        android:id="@+id/submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="Post"
        app:layout_constraintBaseline_toBaselineOf="@+id/post_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/post_text"/>


</androidx.constraintlayout.widget.ConstraintLayout>
// android/app/src/main/java/io/getstream/thestream/services/FeedService.kt:40
fun post(message: String) {
    val feed = client.flatFeed("user")
    feed.addActivity(
        Activity
            .builder()
            .actor("SU:${user}")
            .verb("post")
            .`object`(UUID.randomUUID().toString())
            .extraField("message", message)
            .build()
    ).join()
}

Here we use Stream Java’s CloudClient from the Cloud package. This set of classes takes our frontend token, which allows the mobile app to communicate directly with Stream. We are authenticated only to post activities for the actor SU:john (SU means Stream User). Since we aren't storing a corresponding object in a database, we generate an id to keep each post unique. We also pass along a message payload, which is what our followers will see.

在这里,我们使用Cloud包中的 Stream Java的CloudClient 。 这组类使用我们的前端令牌,该令牌使移动应用程序可以直接与Stream进行通信。 我们仅通过身份验证才能发布演员SU:john的活动 (SU表示流用户)。 由于我们没有在数据库中存储相应的对象,因此我们生成一个ID以使每个帖子保持唯一。 我们还将传递消息有效负载,这是我们的关注者将看到的内容。

You may be wondering what the client.flatFeed("user") is referring to. For this to work, we need to set up a flat feed called "user" in Stream. This is where every user's feed, which only contains their messages, will be stored. Later we'll see how one user can follow another user's feed.

您可能想知道client.flatFeed("user")指的是什么。 为此,我们需要在Stream中设置一个称为“用户”的固定供稿。 这是每个用户的供稿(仅包含其消息)的存储位置。 稍后,我们将看到一个用户如何关注另一个用户的供稿。

Inside of your Stream development app create a flat feed called “user”:

在您的Stream开发应用程序内部,创建一个称为“用户”的平面供稿:

Image for post

That’s all we need for Stream to store our messages. Once all of the functions return, we return to the user’s profile screen to display our posted messages. We’ll build this next.

这就是Stream存储消息所需要的。 一旦所有功能返回,我们将返回到用户的个人资料屏幕以显示我们发布的消息。 接下来,我们将进行构建。

步骤3:在我们的个人资料上显示消息 (Step 3: Displaying Messages on our Profile)

Let’s first build a fragment that will contain the user’s messages. Here is what the screen will look like:

让我们首先构建一个片段,其中将包含用户的消息。 屏幕显示如下:

Image for post

And here is the code for the ProfileFragment:

这是ProfileFragment的代码:

class ProfileFragment : Fragment(), CoroutineScope by MainScope() {
    private lateinit var adapter: FeedAdapter


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val rootView: View = inflater.inflate(R.layout.fragment_profile, container, false)
        val listView: ListView = rootView.findViewById(R.id.list_profile_feed)


        adapter = FeedAdapter(rootView.context, mutableListOf())
        listView.adapter = adapter


        val newPost: View = rootView.findViewById(R.id.new_post)
        newPost.setOnClickListener {
            startActivityForResult(
                Intent(rootView.context, CreatePostActivity::class.java),
                POST_SUCCESS
            )
        }


        loadProfileFeed()


        return rootView
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)


        if (resultCode == POST_SUCCESS) {
            loadProfileFeed()
        }
    }


    private fun loadProfileFeed() {
        launch(Dispatchers.IO) {
            val profileFeed = FeedService.profileFeed()


            launch(Dispatchers.Main) {
                adapter.clear()
                adapter.addAll(profileFeed)
            }
        }
    }
}

And the layout:

以及布局:

<!-- android/app/src/main/res/layout/fragment_profile.xml -->
<?xml version="1.0" encoding="utf-8"?>
<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=".ProfileFragment">


    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/new_post"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        android:src="@drawable/ic_add_white_24dp"/>


    <ListView
        android:id="@+id/list_profile_feed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</FrameLayout>

The ProfileFragment does two things. First, it has a floating action button that starts our CreatePostActivity, which we saw in Step 2, and handles the result. Second, it loads their feed and displays those messages.

ProfileFragment做两件事。 首先,它具有一个浮动操作按钮,该按钮启动我们在步骤2中看到的CreatePostActivity ,并处理结果。 其次,它加载其提要并显示这些消息。

Let’s see how we load our messages via FeedService.profileFeed() invoked inside of loadProfileFeed:

让我们看看如何通过在loadProfileFeed内部调用的FeedService.profileFeed()加载消息:

// android/app/src/main/java/io/getstream/thestream/services/FeedService.kt:33
fun profileFeed(): MutableList<Activity> {
    return client
        .flatFeed("user")
        .getActivities(Limit(25))
        .join()
}

Using this result, we pass the activities into a FeedAdapter which backs a simple ListAdapter. Here you can see how to use the raw results from Stream however you want. In this case, we'll simply display the activity's message and author:

利用这个结果,我们通过活动为FeedAdapter它备份一个简单的ListAdapter 。 在这里,您可以查看如何根据需要使用Stream的原始结果。 在这种情况下,我们将仅显示活动的消息和作者:

// android/app/src/main/java/io/getstream/thestream/FeedAdapter.kt:13
class FeedAdapter(context: Context, objects: MutableList<Activity>) :
    ArrayAdapter<Activity>(context, android.R.layout.simple_list_item_1, objects) {


    private data class ViewHolder(
        val author: TextView,
        val message: TextView
    )


    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val streamActivity: Activity = getItem(position)!!
        val viewHolder: ViewHolder
        var newView = convertView


        if (newView == null) {
            val inflater = LayoutInflater.from(context)
            newView = inflater.inflate(R.layout.feed_item, parent, false)
            viewHolder = ViewHolder(
                newView.timeline_item_author_name as TextView,
                newView.timeline_item_message as TextView
            )
        } else {
            viewHolder = newView.tag as ViewHolder
        }


        viewHolder.author.text = streamActivity.actor.replace("SU:", "")
        viewHolder.message.text = streamActivity.extra["message"] as String


        newView!!.tag = viewHolder


        return newView
    }
}

Next, we’ll see how to follow multiple users via a timeline feed.

接下来,我们将看到如何通过时间轴feed跟踪多个用户。

用户时间表 (User Timeline)

Now that users can post messages, we’d like to follow a few and see a combined feed of all the messages for users we follow.

现在用户可以发布消息了,我们希望关注一些消息,并为我们关注的用户查看所有消息的合并供稿。

步骤1:追踪使用者 (Step 1: Follow a User)

The first thing we need to do is view a list of users and pick a few to follow. We’ll start by creating a view that shows all the users and lets a user follow a few. Here is the screen that shows all the users:

我们需要做的第一件事是查看用户列表并选择一些用户。 我们将首先创建一个显示所有用户并让用户关注的视图。 这是显示所有用户的屏幕:

Image for post

This is backed by a PeopleFragment:

这由PeopleFragment支持:

// android/app/src/main/java/io/getstream/thestream/PeopleFragment.kt:21
class PeopleFragment : Fragment(), CoroutineScope by MainScope() {


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val rootView: View = inflater.inflate(R.layout.fragment_people, container, false)
        val list: ListView = rootView.findViewById(R.id.list_people)


        val adapter = ArrayAdapter(
            rootView.context,
            android.R.layout.simple_list_item_1,
            mutableListOf<String>()
        )
        list.adapter = adapter


        list.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            val alertDialogBuilder: AlertDialog.Builder = AlertDialog.Builder(rootView.context)


            alertDialogBuilder.setTitle("Pick an action")
            alertDialogBuilder.setPositiveButton("Follow") { dialog, _ ->
                val otherUser = adapter.getItem(position).toString()
                FeedService.follow(otherUser)


                dialog.dismiss()


                Toast
                    .makeText(context, "Successfully followed $otherUser", Toast.LENGTH_LONG)
                    .show()
            }


            alertDialogBuilder.setNegativeButton("Close") { dialog, _ ->
                dialog.dismiss()
            }


            alertDialogBuilder.show()
        }


        launch(Dispatchers.IO) {
            val users = BackendService.getUsers()


            launch(Dispatchers.Main) { adapter.addAll(users) }
        }


        return rootView
    }


}

And the layout:

以及布局:

<!-- android/app/src/main/res/layout/fragment_people.xml:1 -->
<?xml version="1.0" encoding="utf-8"?>


<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=".PeopleFragment">


    <ListView
        android:id="@+id/list_people"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</FrameLayout>
// android/app/src/main/java/io/getstream/thestream/services/BackendService.kt:42
fun getUsers(): List<String> {
    val request = Request.Builder()
        .url("$apiRoot/v1/users")
        .addHeader("Authorization", "Bearer $authToken")
        .get()


    http.newCall(request.build()).execute().use { response ->
        val jsonArray = JSONObject(response.body!!.string()).getJSONArray("users")


        return List(jsonArray.length()) { i ->
            jsonArray.get(i).toString()
        }.filterNot { it == user }
    }
}

The backend is a mock implementation that simply stores the users in an object. We won't go into this here, but refer to source if exciting and be sure to back this with a real implementation in your production application.

backend是一个模拟实现,仅将用户存储在一个对象中。 我们不会在这里进行讨论,但是如果令人兴奋,请参考源代码,并确保在您的生产应用程序中使用真实的实现来支持它。

Once we have our users, we build our list and bind a click listener. This listener will pop open an alert dialog that allows us to follow a user. If a user chooses to follow a user, we call FeedService.follow:

拥有用户后,我们将建立列表并绑定一个Click侦听器。 该侦听器将弹出打开一个警告对话框,允许我们关注用户。 如果用户选择关注用户,我们将调用FeedService.follow

// android/app/src/main/java/io/getstream/thestream/services/FeedService.kt:19
fun follow(otherUser: String) {
    client
        .flatFeed("timeline")
        .follow(client.flatFeed("user", otherUser))
        .join()
}

Here we’re adding a follow relationship to another user’s user feed to this user's timeline feed. All this means is anytime a user post to their user feed (implemented in the first part), we'll see it on our timeline feed. The cool part is, we can add any number of users feeds to our timeline feed, and Stream will return a well-ordered list of activities.

在这里,我们将关注关系添加到另一个用户的user提要到该用户的timeline提要。 所有这些意味着,只要用户在其用户供稿中发布(在第一部分中实现),我们就会在timeline供稿中看到它。 最酷的部分是,我们可以将任意数量的用户供稿添加到我们的timeline供稿中,Stream会返回一个井井有条的活动列表。

Since we have a new feed type, we need to set this up in Stream. Just like the user feed, navigate to the Stream app you set up and create a flat feed group called timeline:

由于我们具有新的Feed类型,因此需要在Stream中进行设置。 就像user供稿一样,导航到您设置的Stream应用,并创建一个称为时间轴的固定供稿组:

Image for post

步骤2:查看时间表 (Step 2: View Timeline)

Now that we have a way to follow users, we can view our timeline. When we’re done, assuming we’ve followed “bob” and “sara” we’ll see a screen that looks like this:

现在我们有了跟踪用户的方式,我们可以查看时间表了。 完成后,假设我们遵循“ bob”和“ sara”,我们将看到一个类似以下的屏幕:

Image for post

Let’s look at the code to display our timeline. We have a TimelineFragment:

让我们看一下显示时间轴的代码。 我们有一个TimelineFragment

// android/app/src/main/java/io/getstream/thestream/TimelineFragment.kt:16
class TimelineFragment : Fragment(), CoroutineScope by MainScope() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val rootView: View = inflater.inflate(R.layout.fragment_timeline, container, false)
        val listView: ListView = rootView.findViewById<View>(R.id.list_timeline) as ListView
        val adapter = FeedAdapter(context!!, mutableListOf())


        listView.adapter = adapter


        launch(Dispatchers.IO) {
            val timelineFeed = FeedService.timelineFeed()
            launch(Dispatchers.Main) { adapter.addAll(timelineFeed) }
        }


        return rootView
    }
}

And our layout:

和我们的布局:

<!-- android/app/src/main/res/layout/fragment_timeline.xml:1 -->
<?xml version="1.0" encoding="utf-8"?>
<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=".TimelineFragment">


    <ListView
        android:id="@+id/list_timeline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>


</FrameLayout>
// android/app/src/main/java/io/getstream/thestream/services/FeedService.kt:26
fun timelineFeed(): MutableList<Activity> {
    return client
        .flatFeed("timeline")
        .getActivities(Limit(25))
        .join()
}

This code is the same as getting our profile, except we ask for our timeline feed instead. And that's it. We now have a fully functioning mini social network powered by Stream.

此代码与获取我们的个人资料相同,只不过我们要求提供timeline供稿。 就是这样。 现在,我们有了一个由Stream提供支持的功能全面的小型社交网络。

翻译自: https://medium.com/swlh/build-a-custom-social-network-timeline-feed-with-kotlin-b068f06119b8

kotlin 自定义内联

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值