= unmodifiableList(listOf(
6 | FoodItem(
7 | "https://pbs.twimg.com/profile_images/726613174523297792/v0edN9KP_400x400.jpg",
8 | "Falafel",
9 | "a deep-fried ball, doughnut or patty made from ground chickpeas, fava beans, or both. " +
10 | "Falafel is a traditional Middle Eastern food, commonly served in a pita, " +
11 | "which acts as a pocket, or wrapped in a flatbread known as taboon; " +
12 | "\"falafel\" also frequently refers to a wrapped sandwich that is prepared in this way.",
13 | NutritionInfo(energy = 333.0, fats = 18.0, carbohydrates = 32.0, proteins = 13.0),
14 | FoodKind.Meal,
15 | 31_300_000
16 | ),
17 | FoodItem(
18 | "https://bigoven-res.cloudinary.com/image/upload/t_recipe-256/tofu-baked-in-a-lemon-rosemary-43d3fe.jpg",
19 | "Tofu",
20 | "a food cultivated by coagulating soy milk and then pressing the resulting curds " +
21 | "into soft white blocks. " +
22 | "It is a component in East Asian, Southeast Asian and West African cuisines.",
23 | NutritionInfo(energy = 76.0, fats = 4.8, carbohydrates = 1.9, proteins = 8.0),
24 | FoodKind.Meal,
25 | 72_100_000
26 | ),
27 | FoodItem(
28 | "https://static.wixstatic.com/media/f012eb_646ac521ce864eb8bd2d13477870f2dd~mv2.png/v1/fill/w_256,h_256,al_c,q_90/file.jpg",
29 | "Cellophane noodles",
30 | "a type of transparent noodle made from starch " +
31 | "(such as mung bean starch, yam, potato starch, cassava, canna or batata starch) and water.",
32 | NutritionInfo(energy = 351.0, fats = .1, carbohydrates = 86.0, proteins = .2),
33 | FoodKind.Meal,
34 | 1_500_000
35 | ),
36 | FoodItem(
37 | "https://s-media-cache-ak0.pinimg.com/736x/94/4d/47/944d47afe47ea473d19264dd83ac0235--china-food-tofu-recipes.jpg",
38 | "Tofu skin",
39 | "a food product made from soybeans. During the boiling of soy milk, in an open shallow pan, " +
40 | "a film or skin forms on the liquid surface.",
41 | NutritionInfo(energy = 387.0, fats = 19.0, carbohydrates = 12.0, proteins = 42.0),
42 | FoodKind.Meal,
43 | 11_800_000
44 | ),
45 | FoodItem(
46 | "https://upload.wikimedia.org/wikipedia/commons/f/f8/Dal_Makhani.jpg",
47 | "Dal moong", // it has nothing in common with Data Access Layer
48 | "various soups prepared from dried, split pulses.",
49 | NutritionInfo(energy = 347.0, fats = 1.0, carbohydrates = 63.0, proteins = 24.0),
50 | FoodKind.Meal,
51 | 818_000
52 | ),
53 | FoodItem(
54 | "http://static.wixstatic.com/media/92eca4_234f5123236c4e2f9032ef39e851d28f.jpg_256",
55 | "Green tea",
56 | "is a type of tea that is made from Camellia sinensis leaves " +
57 | "that have not undergone the same withering and oxidation process " +
58 | "used to make oolong and black tea. " +
59 | "Green tea originated in China, but its production has spread to many countries in Asia.",
60 | NutritionInfo(energy = 2.0, fats = .0, carbohydrates = .47, proteins = .0),
61 | FoodKind.Drink,
62 | 92_200_000
63 | ),
64 | FoodItem(
65 | "http://www.rivertea.com/blog/wp-content/uploads/2013/01/black-tea-cup-e1359634422907.jpg",
66 | "Black tea",
67 | "is a type of tea that is more oxidized than oolong, green and white teas. " +
68 | "Black tea is generally stronger in flavor than the less oxidized teas.",
69 | NutritionInfo(energy = 1.1, fats = .0, carbohydrates = .3, proteins = .0),
70 | FoodKind.Drink,
71 | 39_000_000
72 | ),
73 | FoodItem(
74 | "https://meileaf.com/uploaded/thumbnails/db_file_img_397_365x365_eaeaec.jpg",
75 | "Oolong",
76 | "is a traditional Chinese tea (Camellia sinensis) " +
77 | "produced through a process including withering the plant under strong sun " +
78 | "and oxidation before curling and twisting.",
79 | NutritionInfo(energy = 1.4, fats = .051, carbohydrates = .04, proteins = .2),
80 | FoodKind.Drink,
81 | 14_000_000
82 | )
83 | ))
84 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/unsafe/FoodListFragment.kt:
--------------------------------------------------------------------------------
1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.unsafe
2 |
3 | import android.app.Fragment
4 | import android.os.Bundle
5 | import android.support.v7.widget.DividerItemDecoration
6 | import android.support.v7.widget.LinearLayoutManager
7 | import android.view.LayoutInflater
8 | import android.view.ViewGroup
9 | import net.aquadc.advancedkotlinpatterns.app
10 | import net.aquadc.advancedkotlinpatterns.common.getEnumSet
11 | import net.aquadc.advancedkotlinpatterns.common.putEnumSet
12 | import net.aquadc.advancedkotlinpatterns.common.recycler.ListAdapter
13 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getFilteredAndSortedFoodItems
14 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getSortedByPopularityFoodItems
15 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind
16 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter
17 | import net.aquadc.advancedkotlinpatterns.recycler.createFoodItemHolder
18 | import org.jetbrains.anko.UI
19 | import org.jetbrains.anko.recyclerview.v7.recyclerView
20 |
21 | /**
22 | * Shows a list of food.
23 | * Popular mode:
24 | * shows a list of food, sorted by descending of popularity (most popular first)
25 | * FilterAndSort:
26 | * shows a list of food of certain [FoodKind]s sorted by specified [NutritionParameter]
27 | */
28 | class FoodListFragment : Fragment() {
29 |
30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI {
31 |
32 | val data = when (arguments.getInt(ModeKey)) {
33 | PopularMode ->
34 | getSortedByPopularityFoodItems()
35 |
36 | FilterAndSortMode ->
37 | getFilteredAndSortedFoodItems(
38 | kinds = arguments.getEnumSet(FoodKindsKey),
39 | sortBy = arguments.getSerializable(SortByParameterKey) as NutritionParameter,
40 | desc = arguments.getBoolean(SortDescKey))
41 |
42 | else ->
43 | throw IndexOutOfBoundsException()
44 | }
45 |
46 | recyclerView {
47 | id = 1
48 | layoutManager = LinearLayoutManager(activity)
49 | adapter = ListAdapter(data) { createFoodItemHolder(app.picasso) }
50 | addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
51 | }
52 |
53 | }.view
54 |
55 | /*
56 | /**
57 | * This companion describes the most unsafe way of passing arguments:
58 | * all constants are public, value types are not documented at all.
59 | * Just create a fragment, instantiate a Bundle for arguments,
60 | * fill it (if you guess how) and watch it burns.
61 | *
62 | * Call-site example for [PopularMode]
63 | * FoodListFragment().apply {
64 | * arguments = Bundle(1).apply {
65 | * putInt(FoodListFragment.ModeKey, FoodListFragment.PopularMode)
66 | * }
67 | * }
68 | *
69 | * Call-site example for [FilterAndSortMode]
70 | * FoodListFragment().apply {
71 | * arguments = Bundle(4).apply {
72 | * putInt(FoodListFragment.ModeKey, FoodListFragment.FilterAndSortMode)
73 | * putEnumSet(FoodListFragment.FoodKindsKey, foodChecks.filterValues { it.isChecked }.keys)
74 | * putSerializable(FoodListFragment.SortByParameterKey, nutritionParameterSpinner.selectedItem as NutritionParameter)
75 | * putBoolean(FoodListFragment.SortDescKey, descendingCheck.isChecked)
76 | * }
77 | * }
78 | */
79 | companion object {
80 | // @formatter:off
81 | const val ModeKey = "mode"
82 | const val PopularMode = 1
83 | const val FilterAndSortMode = 2
84 | const val FoodKindsKey = "food kinds"
85 | const val SortByParameterKey = "sort by"
86 | const val SortDescKey = "desc"
87 | // @formatter:on
88 | }
89 | */
90 |
91 | /**
92 | * This Companion describes a safer way to declare arguments contract:
93 | * all constants are private and used by factories,
94 | * and call-site just assumes that factories are valid & correct.
95 | *
96 | * Call-site example for [popular] mode
97 | * `FoodListFragment.popular()`
98 | * (method reference: `FoodListFragment.Companion::popular`)
99 | *
100 | * Call-site example for [filterAndSort] mode
101 | * FoodListFragment.filterAndSort(
102 | * kinds = foodChecks.filterValues { it.isChecked }.keys,
103 | * sortBy = nutritionParameterSpinner.selectedItem as NutritionParameter,
104 | * desc = descendingCheck.isChecked)
105 | */
106 | companion object {
107 | // @formatter:off
108 | private const val ModeKey = "mode"
109 | private const val PopularMode = 1
110 | private const val FilterAndSortMode = 2
111 | private const val FoodKindsKey = "food kinds"
112 | private const val SortByParameterKey = "sort by"
113 | private const val SortDescKey = "desc"
114 | // @formatter:on
115 |
116 | fun popular() = FoodListFragment().apply {
117 | arguments = Bundle(1).apply {
118 | putInt(ModeKey, PopularMode)
119 | }
120 | }
121 |
122 | fun filterAndSort(kinds: Set, sortBy: NutritionParameter, desc: Boolean) =
123 | FoodListFragment().apply {
124 | arguments = Bundle(4).apply {
125 | putInt(ModeKey, FilterAndSortMode)
126 | putEnumSet(FoodKindsKey, kinds)
127 | putSerializable(SortByParameterKey, sortBy)
128 | putBoolean(SortDescKey, desc)
129 | }
130 | }
131 | }
132 |
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/LocalBroadcastManager.java:
--------------------------------------------------------------------------------
1 | package net.aquadc.advancedkotlinpatterns.feature.bind;
2 |
3 | /*
4 | * Copyright (C) 2011 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.content.BroadcastReceiver;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.content.IntentFilter;
23 | import android.net.Uri;
24 | import android.os.Handler;
25 | import android.os.Message;
26 | import android.util.Log;
27 |
28 | import java.util.ArrayList;
29 | import java.util.HashMap;
30 | import java.util.Set;
31 |
32 | /**
33 | * Helper to register for and send broadcasts of Intents to local objects
34 | * within your process. This has a number of advantages over sending
35 | * global broadcasts with {@link android.content.Context#sendBroadcast}:
36 | *
37 | * - You know that the data you are broadcasting won't leave your app, so
38 | * don't need to worry about leaking private data.
39 | *
- It is not possible for other applications to send these broadcasts to
40 | * your app, so you don't need to worry about having security holes they can
41 | * exploit.
42 | *
- It is more efficient than sending a global broadcast through the
43 | * system.
44 | *
45 | */
46 | public final class LocalBroadcastManager {
47 | private static class ReceiverRecord {
48 | final IntentFilter filter;
49 | final BroadcastReceiver receiver;
50 | boolean broadcasting;
51 |
52 | ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
53 | filter = _filter;
54 | receiver = _receiver;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return "Receiver{" +
60 | receiver +
61 | " filter=" +
62 | filter +
63 | "}";
64 | }
65 | }
66 |
67 | private static class BroadcastRecord {
68 | final Intent intent;
69 | final ArrayList receivers;
70 |
71 | BroadcastRecord(Intent _intent, ArrayList _receivers) {
72 | intent = _intent;
73 | receivers = _receivers;
74 | }
75 | }
76 |
77 | private static final String TAG = "LocalBroadcastManager";
78 | private static final boolean DEBUG = false;
79 |
80 | private final Context mAppContext;
81 |
82 | private final HashMap> mReceivers = new HashMap<>();
83 | private final HashMap> mActions = new HashMap<>();
84 |
85 | private final ArrayList mPendingBroadcasts = new ArrayList<>();
86 |
87 | static final int MSG_EXEC_PENDING_BROADCASTS = 1;
88 |
89 | private final Handler mHandler;
90 |
91 | private static final Object mLock = new Object();
92 | private static LocalBroadcastManager mInstance;
93 |
94 | public static LocalBroadcastManager getInstance(Context context) {
95 | synchronized (mLock) {
96 | if (mInstance == null) {
97 | mInstance = new LocalBroadcastManager(context.getApplicationContext());
98 | }
99 | return mInstance;
100 | }
101 | }
102 |
103 | private LocalBroadcastManager(Context context) {
104 | mAppContext = context;
105 | mHandler = new Handler(context.getMainLooper()) {
106 |
107 | @Override
108 | public void handleMessage(Message msg) {
109 | switch (msg.what) {
110 | case MSG_EXEC_PENDING_BROADCASTS:
111 | executePendingBroadcasts();
112 | break;
113 | default:
114 | super.handleMessage(msg);
115 | }
116 | }
117 | };
118 | }
119 |
120 | /**
121 | * Register a receive for any local broadcasts that match the given IntentFilter.
122 | *
123 | * @param receiver The BroadcastReceiver to handle the broadcast.
124 | * @param filter Selects the Intent broadcasts to be received.
125 | *
126 | * @see #unregisterReceiver
127 | */
128 | public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
129 | synchronized (mReceivers) {
130 | ReceiverRecord entry = new ReceiverRecord(filter, receiver);
131 | ArrayList filters = mReceivers.get(receiver);
132 | if (filters == null) {
133 | filters = new ArrayList<>(1);
134 | mReceivers.put(receiver, filters);
135 | }
136 | filters.add(filter);
137 | for (int i=0; i entries = mActions.get(action);
140 | if (entries == null) {
141 | entries = new ArrayList<>(1);
142 | mActions.put(action, entries);
143 | }
144 | entries.add(entry);
145 | }
146 | }
147 | }
148 |
149 | /**
150 | * Unregister a previously registered BroadcastReceiver. All
151 | * filters that have been registered for this BroadcastReceiver will be
152 | * removed.
153 | *
154 | * @param receiver The BroadcastReceiver to unregister.
155 | *
156 | * @see #registerReceiver
157 | */
158 | public void unregisterReceiver(BroadcastReceiver receiver) {
159 | synchronized (mReceivers) {
160 | ArrayList filters = mReceivers.remove(receiver);
161 | if (filters == null) {
162 | return;
163 | }
164 | for (int i=0; i receivers = mActions.get(action);
169 | if (receivers != null) {
170 | for (int k=0; k categories = intent.getCategories();
203 |
204 | final boolean debug = DEBUG ||
205 | ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
206 | if (debug) Log.v(
207 | TAG, "Resolving type " + type + " scheme " + scheme
208 | + " of intent " + intent);
209 |
210 | ArrayList entries = mActions.get(intent.getAction());
211 | if (entries != null) {
212 | if (debug) Log.v(TAG, "Action list: " + entries);
213 |
214 | ArrayList receivers = null;
215 | for (int i=0; i= 0) {
229 | if (debug) Log.v(TAG, " Filter matched! match=0x" +
230 | Integer.toHexString(match));
231 | if (receivers == null) {
232 | receivers = new ArrayList<>();
233 | }
234 | receivers.add(receiver);
235 | receiver.broadcasting = true;
236 | } else {
237 | if (debug) {
238 | String reason;
239 | switch (match) {
240 | case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
241 | case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
242 | case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
243 | case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
244 | default: reason = "unknown reason"; break;
245 | }
246 | Log.v(TAG, " Filter did not match: " + reason);
247 | }
248 | }
249 | }
250 |
251 | if (receivers != null) {
252 | for (int i=0; i